"""
MIT License

Copyright (c) 2020 PyKOB - MorseKOB in Python

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

"""
kob module

Handles external key and/or sounder interfaced by Raspberry Pi GPIO.
The class inherits from class KOB in file kob.py.  __init__, Key, and sounder methods are overridden.
"""

import sys
import threading
import time
from enum import Enum, IntEnum, unique
from pykob import audio, config, log
from pykob.kob import KOB  # import the KOB class, super-class of KOB_GPIO.
try:
    from gpiozero import LED, Button
    gpioAvailable = True
except:
    log.log("gpiozero module not installed.")
    gpioAvailable = False

DEBOUNCE  = 0.015  # time to ignore transitions due to contact bounce (sec)
CODESPACE = 0.120  # amount of space to signal end of code sequence (sec)
CKTCLOSE  = 0.75  # length of mark to signal circuit closure (sec)

if sys.platform == 'win32':
    from ctypes import windll
    windll.winmm.timeBeginPeriod(1)  # set clock resoluton to 1 ms (Windows only)

@unique
class CodeSource(IntEnum):
    local = 1
    wire = 2
    player = 3

#  KOB_GPIO inherits from KOB.  Only the __init__, key, and setSounder methods are overwritten.
class KOB_GPIO(KOB):
    
    def __init__(
            self, port=None, interfaceType=config.interface_type.loop,
            audio=False, callback=None):
        self.t0 = -1.0  ### ZZZ Keep track of when the playback started
        self.callback = callback
        self.audio = audio
        self.interfaceType = interfaceType
        self.sdrState = False  # True: mark, False: space
        self.tLastSdr = time.time()  # time of last sounder transition
        if gpioAvailable:
            try:
                self.gpi = Button(21, pull_up = True)  # GPIO21 is key input.
                self.gpo = LED(26)  # GPIO26 used to drive sounder.
                print("The GPIO interface is active.")
            except:
                log.info("Interface for key and/or sounder on GPIO not available. Key and sounder will not function.")
                self.gpi = None
                self.gpo = None
                self.callback = None
        else:
            self.gpi = None
            self.gpo = None
            self.callback = None
        self.setSounder(True)  # Close the circuit, sounder should pull in.
        time.sleep(0.5)  # ZZZ Why is this here?
        if self.gpi:
            try:
                self.keyState = self.gpi.is_pressed if not config.invert_key_input else not self.gpi.is_pressed  # True: closed, False: open
                self.tLastKey = time.time()  # time of last key transition
                self.cktClose = self.keyState  # True: circuit latched closed
                if self.interfaceType == config.interface_type.key_sounder:
                    self.setSounder(self.keyState)
            except(OSError):
                log.err("GPIO not available.")
                self.gpi = None
        self.recorder = None
        if self.callback:
            keyreadThread = threading.Thread(name='KOB-KeyRead', daemon=True, target=self.callbackRead)
            keyreadThread.start()


    def key(self):
        code = ()
        while True:
            t = time.time()
            if self.gpi.is_pressed != self.keyState:  #  Do something if state changed.
                self.keyState = self.gpi.is_pressed   #  Record new state.
                dt = int((t - self.tLastKey) * 1000)
                self.tLastKey = t
                if self.interfaceType == config.interface_type.key_sounder:
                    self.setSounder(self.gpi.is_pressed)   #  Change sounder state in the case of independent key and sounder.
                time.sleep(DEBOUNCE)
                if self.gpi.is_pressed:
                    code += (-dt,)
                elif self.cktClose:
                    code += (-dt, +2)  # unlatch closed circuit
                    self.cktClose = False
                    return code
                else:
                    code += (dt,)
            if not self.gpi.is_pressed and code and \
                    t > self.tLastKey + CODESPACE:
                return code
            if self.gpi.is_pressed and not self.cktClose and \
                    t > self.tLastKey + CKTCLOSE:
                code += (+1,)  # latch circuit closed
                self.cktClose = True
                return code
            if len(code) >= 50:  # code sequences can't have more than 50 elements
                return code
            time.sleep(0.001)
        return ""

    def setSounder(self, state):
        try:
            if state != self.sdrState:
                self.sdrState = state
                if state:
                    if self.gpo:
                        self.gpo.on()   # GPO goes HIGH.
                    if self.audio:
                        audio.play(1)  # click
                else:
                    if self.gpo:
                        self.gpo.off()  # GPO goes LOW.
                    if self.audio:
                        audio.play(0)  # clack
        except(OSError):
            log.err("GPIO and audio not available.  Enable one.")
            self.audio = None

"""
MIT License

Copyright (c) 2020 PyKOB - MorseKOB in Python

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

"""
kob module

Handles external key and/or sounder interfaced by Raspberry Pi GPIO.
The class inherits from class KOB in file kob.py.  __init__, Key, and sounder methods are overridden.
"""

import sys
import threading
import time
from enum import Enum, IntEnum, unique
from pykob import audio, config, log
from pykob.kob import KOB  # import the KOB class, super-class of KOB_GPIO.
try:
    from gpiozero import LED, Button
    gpioAvailable = True
except:
    log.log("gpiozero module not installed.")
    gpioAvailable = False

DEBOUNCE  = 0.015  # time to ignore transitions due to contact bounce (sec)
CODESPACE = 0.120  # amount of space to signal end of code sequence (sec)
CKTCLOSE  = 0.75  # length of mark to signal circuit closure (sec)

if sys.platform == 'win32':
    from ctypes import windll
    windll.winmm.timeBeginPeriod(1)  # set clock resoluton to 1 ms (Windows only)

@unique
class CodeSource(IntEnum):
    local = 1
    wire = 2
    player = 3

#  KOB_GPIO inherits from KOB.  Only the __init__, key, and setSounder methods are overwritten.
class KOB_GPIO(KOB):
    
    def __init__(
            self, port=None, interfaceType=config.interface_type.loop,
            audio=False, callback=None):
        self.t0 = -1.0  ### ZZZ Keep track of when the playback started
        self.callback = callback
        self.audio = audio
        self.interfaceType = interfaceType
        self.sdrState = False  # True: mark, False: space
        self.tLastSdr = time.time()  # time of last sounder transition
        if gpioAvailable:
            try:
                self.gpi = Button(21, pull_up = True)  # GPIO21 is key input.
                self.gpo = LED(26)  # GPIO26 used to drive sounder.
                print("The GPIO interface is active.")
            except:
                log.info("Interface for key and/or sounder on GPIO not available. Key and sounder will not function.")
                self.gpi = None
                self.gpo = None
                self.callback = None
        else:
            self.gpi = None
            self.gpo = None
            self.callback = None
        self.setSounder(True)  # Close the circuit, sounder should pull in.
        time.sleep(0.5)  # ZZZ Why is this here?
        if self.gpi:
            try:
                self.keyState = self.gpi.is_pressed if not config.invert_key_input else not self.gpi.is_pressed  # True: closed, False: open
                self.tLastKey = time.time()  # time of last key transition
                self.cktClose = self.keyState  # True: circuit latched closed
                if self.interfaceType == config.interface_type.key_sounder:
                    self.setSounder(self.keyState)
            except(OSError):
                log.err("GPIO not available.")
                self.gpi = None
        self.recorder = None
        if self.callback:
            keyreadThread = threading.Thread(name='KOB-KeyRead', daemon=True, target=self.callbackRead)
            keyreadThread.start()


    def key(self):
        code = ()
        while True:
            t = time.time()
            if self.gpi.is_pressed != self.keyState:  #  Do something if state changed.
                self.keyState = self.gpi.is_pressed   #  Record new state.
                dt = int((t - self.tLastKey) * 1000)
                self.tLastKey = t
                if self.interfaceType == config.interface_type.key_sounder:
                    self.setSounder(self.gpi.is_pressed)   #  Change sounder state in the case of independent key and sounder.
                time.sleep(DEBOUNCE)
                if self.gpi.is_pressed:
                    code += (-dt,)
                elif self.cktClose:
                    code += (-dt, +2)  # unlatch closed circuit
                    self.cktClose = False
                    return code
                else:
                    code += (dt,)
            if not self.gpi.is_pressed and code and \
                    t > self.tLastKey + CODESPACE:
                return code
            if self.gpi.is_pressed and not self.cktClose and \
                    t > self.tLastKey + CKTCLOSE:
                code += (+1,)  # latch circuit closed
                self.cktClose = True
                return code
            if len(code) >= 50:  # code sequences can't have more than 50 elements
                return code
            time.sleep(0.001)
        return ""

    def setSounder(self, state):
        try:
            if state != self.sdrState:
                self.sdrState = state
                if state:
                    if self.gpo:
                        self.gpo.on()   # GPO goes HIGH.
                    if self.audio:
                        audio.play(1)  # click
                else:
                    if self.gpo:
                        self.gpo.off()  # GPO goes LOW.
                    if self.audio:
                        audio.play(0)  # clack
        except(OSError):
            log.err("GPIO and audio not available.  Enable one.")
            self.audio = None

MIT License  Copyright (c) 2020 PyKOB - MorseKOB in Python  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. kob module  Handles external key and/or sounder interfaced by Raspberry Pi GPIO. The class inherits from class KOB in file kob.py.  __init__, Key, and sounder methods are overridden. # import the KOB class, super-class of KOB_GPIO. # time to ignore transitions due to contact bounce (sec) # amount of space to signal end of code sequence (sec) # length of mark to signal circuit closure (sec) # set clock resoluton to 1 ms (Windows only) #  KOB_GPIO inherits from KOB.  Only the __init__, key, and setSounder methods are overwritten. ### ZZZ Keep track of when the playback started # True: mark, False: space # time of last sounder transition # GPIO21 is key input. # GPIO26 used to drive sounder. # Close the circuit, sounder should pull in. # ZZZ Why is this here? # True: closed, False: open # time of last key transition # True: circuit latched closed #  Do something if state changed. #  Record new state. #  Change sounder state in the case of independent key and sounder. # unlatch closed circuit # latch circuit closed # code sequences can't have more than 50 elements # GPO goes HIGH. # click # GPO goes LOW. # clack