#!/usr/bin/python3
#
# RPi python script to control a DDS-VFO
# via an IR remote control and/or hamlib protocol over TCPIP
# Implementation for a dds9850 or dds9833 module
# Copyright (C) Herbert Hanewinkel
#
# Version 1.1
# Support for mouse events added
# mouse wheel changes frequency up/down
# mouse left/right button change stepsize

# HamlibHandler code adapted from: 
  # hamlibserver.py
  # This software is Copyright (C) 2012 by James C. Ahlstrom, and is
  # licensed for use under the GNU General Public License (GPL).

import RPi.GPIO as GPIO
import sys
import evdev
import signal, os
import socket
import string
from evdev import ecodes
from select import select
from os.path import exists
from time import sleep

########################################################################

# DDS to RPi GPIO connections (plus ground):
FSYNC   = 9                       # GPIO-OUT SPI sync
SCLK    = 10                      # GPIO-OUT SPI clock
SDAT    = 11                      # GPIO-OUT SPI data
pins    = [FSYNC, SCLK, SDAT]
DDSCHIP = 9850                    # type of dds chip, 9833 or 9850

# TRX to RPi GPIO connections (plus ground)
PTT      = 26                     # GPIO-OUT high for transmit
TRX_ON   = 13                     # GPIO-IN  high on TRX power
TX_ON    = 5                      # GPIO-IN  high on transmit
TX_CW    = 6                      # GPIO-IN  high on CW transmit

# TRX specific values
# The TS120S vfo has a 500kHz range from 5.5 to 6. Mhz
# The TS120S has a mechanical three position mode switch
# and a nine position band switch, no connector for electronic
# readout of the switch positions.
FREQ_MIN   = 5500000              # min vfo freq
FREQ_MAX   = 6000000              # max vfo freq
MODE       = ["CW", "USB", "LSB"] #supported modes
NFWIDTH    = 2400
#supported bands
BAND       = [3500000, 7000000, 14000000, 21000000, 28000000, 28500000,
              29000000, 29500000]

# constant values
FREQ_FT8   = 5574000              # memory presets
FREQ_PSK   = 5580000
FREQ_4     = 5600000
FREQ_5     = 5800000
CW_TXSHIFT = 700                  # cw tx offset in hz
STEPS      = [5000, 10, 100, 1000, 10000]
PORT       = 4575                 # port of hamlib protocol
lockfile   = '/run/lock/dds'      # lock file
freqfile   = '/var/dds/lastfreq'  # file for saving frequency and state

########################################################################
#
# state of dds vfo

class DDS:
  def __init__(self):
      # DDS-VFO data structure
      self.dignum      = 0           # number created from digits
      self.digidx      = 0           # digit counter
      self.save        = 0           # used by 2-key seq
      self.memfreq     = [FREQ_MIN,FREQ_FT8,FREQ_PSK,FREQ_4,FREQ_5]
      self.act         = 0           # index of active vfo
      self.band        = 2           # index of selected band
      self.step        = 2           # index of tuning steps
      self.txi         = 0           # on split op, index of tx vfo
      self.ptt         = 0           # internal ptt on/off
      self.txon        = 0           # state of trx: tx on/off
      self.cwshift     = 0           # cw shift active
      self.splitmode   = 0           # split vfo for rx/tx
      self.vfofreq     = [FREQ_FT8, FREQ_MIN]
      self.freq        = [BAND[self.band], BAND[self.band]]
      self.vfo         = ["VFOA", "VFOB"]
      self.last        = self.vfofreq[0]
      self.mode        = 1           # index of mode
  
      # fixed value for hamlb requests
      self.bandwidth   = NFWIDTH     # NF bandwidth of TRX

  def Init(self):
     return self

########################################################################
#
#   Low-level RPi GPIO routines
#   These routines access GPIO directly

def SetPin(pin, value):
    #sets the GPIO pin to desired value (1=on, 0=off)
    GPIO.output(pin, value)

def InitIO():
    GPIO.setmode(GPIO.BCM)
    GPIO.setwarnings(False)
    for pin in pins:
        GPIO.setup(pin,GPIO.OUT)
        SetPin(pin, 0)

    GPIO.setup(PTT, GPIO.OUT)        # PTT
    SetPin(PTT, 0)
    GPIO.setup(TRX_ON, GPIO.IN)     # TRX power on/off
    GPIO.setup(TX_ON, GPIO.IN)      # TX on/off
    GPIO.setup(TX_CW, GPIO.IN)      # TX CW on/off

########################################################################
#
#   DDS routines:

def BeginDDS():
    #Start with FSYNC & SCLK lines high
    SetPin(FSYNC,1)
    SetPin(SCLK,1)

def FinalDDS():
    #Set SCLK, SDAT, FSYNC lines low to avoid output current
    SetPin(SCLK,0)
    SetPin(SDAT,0)
    SetPin(FSYNC,0)

def PulseClock():       #pulses the DDS serial clock line LOW
    SetPin(SCLK,0)      #bit clocked on high-low transition
    SetPin(SCLK,1)      #no delay since python is slow

# -------------------------------------------------------------
#Send data word = 16 serial bits to DDS9833

#FSYNC is low for duration of data transfer
#SCLK is pulsed low to clock in each bit

def Shift16 (data):
    SetPin(FSYNC,0);                  #enable data input to DDS
    for b in range(16):               #loop for 16 data bits
        value = data & 0x8000         #look at left-most bit
        SetPin(SDAT,value)            #puts its value on data line
        data <<= 1                    #shift data bits to left
        data &= 0xFFFF                #limit data to 16 bits
        PulseClock()                  #clock in the data bit
    SetPin(FSYNC,1)                   #brink FSYNC high after word sent

def OutputReg(i):                     # 9833 has two output registers
    BeginDDS()
    if(i == 0): Shift16(0x2000)       #sends DDS register0 to output
    else:       Shift16(0x2800)       #sends DDS register1 to output
    FinalDDS()

def Reset9833():
    BeginDDS()
    Shift16(0x2100)                   #this is DDS reset command
    Shift16(0x2000)                   #sends DDS register0 to output
    FinalDDS()

def SetFreq9833(i, hz):
    #input = frequency in Hz;  result: sends command to DDS
    #this routine converts requested Hz into a DDS register value
    #the conversion factor depends on the master oscillator input
    #If DDS uses 25 MHz oszi, factor = 2^28/25,000,000

    factor = 10.73741824        #assumes 25 Mhz oscillator
    reg = int(hz * factor)      #convert Hz to DDS register value

    #divide 28-bit register value into 14-bit halves
    regLo = reg & 0x3FFF
    regHi = reg >> 14

    if(i == 0):                 # prefix each half with reg0 command
        regLo |= 0x4000
        regHi |= 0x4000
    else:                       # prefix each half with reg1 command
        regLo |= 0x8000
        regHi |= 0x8000

    BeginDDS()
    Shift16(regLo)              #send both halves to the DDS
    Shift16(regHi)
    FinalDDS()
##    print("Freq: ", i, hz)

# --------------------------------------------------------------
# Send data word = 40 bits to DDS9850

def ShiftControl(data):
    for b in range(8):          #loop for 8 bits
        data &= 0xff            # mask out other bits
        value = data & 0x1      #look at left-most bit
        SetPin(SDAT,value)      #puts its value on data line
        data >>= 1              #shift data bits to right
        PulseClock()            #clock in the data bit

def Shift40(data, control):
    BeginDDS()
    SetPin(FSYNC,0)             #enable data input to DDS
    for b in range(32):         #loop for 32 data bits
        value = data & 0x1      #look at left-most bit
        SetPin(SDAT,value)      #puts its value on data line
        data >>= 1              #shift data bits to right
        PulseClock()            #clock in the data bit
    ShiftControl(control)       #append control byte
    SetPin(FSYNC,1)             #brink FSYNC high after word sent
    FinalDDS()

def Reset9850():
    BeginDDS()
    SetPin(FSYNC,0)             #enable data input to DDS
    ShiftControl(0)             #this is DDS reset command
    SetPin(FSYNC,1)             #brink FSYNC high after word sent
    FinalDDS()

def SetFreq9850(hz):
    #input = frequency in Hz;  result: sends command to DDS
    #this routine converts requested Hz into a DDS register value
    #the conversion factor depends on the master oscillator input
    #factor = 2^32/125,000,000

    factor = 34.35973837        #assumes 125 Mhz oscillator
    reg = int(hz * factor)      #convert Hz to DDS register value
    Shift40(reg, 0)

# --------------------------------------------------------------

def ResetDDS():
    if(DDSCHIP == 9833): Reset9833()
    else:                Reset9850() 

# dds9833 chip has two output registers

def SetDDS(i, hz):
    if(DDSCHIP == 9833):
        if(len(dds.vfo) == 2):
            SetFreq9833(i, hz)
            OutputReg(i)
        else:
            SetFreq9833(0, hz)
            OutputReg(0)
    else:
        SetFreq9850(hz)

# switch dds9833 output register only

def ChgDDS(i, hz):
    if(DDSCHIP == 9833 and len(dds.vfo) == 2): OutputReg(i)
    else:                                      SetDDS(0, hz)

# --------------------------------------------------------------
# create a lock file to inhibit running multiple instances
# saves state+freq+vfo to this file
# file contents is read by RPi status display script

def LockDDS(trx):
    i = trx.act
    trx.freq[i] = trx.vfofreq[i] - FREQ_MIN + BAND[trx.band]
    if(trx.txon):
        s = "TX: "
        if(trx.splitmode == 1): i = trx.txi
    else:
        s = "RX: "
    s += trx.vfo[i][-1]
    if(trx.splitmode == 1): s += "s "
    else: s += " "
    try:
        # to get a string format of 00.000.00
        s += (f"{trx.freq[i]:,}")[0:-1]
    except:
        pass

    s += " " + MODE[trx.mode][0:1]
    s += f"{(trx.step):}"
##    print(s)
    with open(lockfile, "w") as fd:
        fd.write(s)
        fd.close()

def LoadState(trx):
    """load VFO state from file"""
    try:
        with open(freqfile, "r") as fd:
            s = fd.read()
            fd.close()
        x = s.split(" ", 6)
        trx.vfofreq[0] = int(x[0])  # VFOA freq
        trx.vfofreq[1] = int(x[1])  # VFOB freq
        if(trx.vfofreq[0] < FREQ_MIN or trx.vfofreq[0] > FREQ_MAX):
            trx.vfofreq[0] = FREQ_MIN
        if(trx.vfofreq[1] < FREQ_MIN or trx.vfofreq[1] > FREQ_MAX):
            trx.vfofreq[1] = FREQ_MIN

        trx.act        = int(x[2])  # active VFO
        if(trx.act >= len(trx.vfo)): trx.act = 0

        trx.txi        = int(x[3])  # split VFO
        if(trx.txi >= len(trx.vfo)): trx.txi = trx.act + 1
        if(trx.txi >= len(trx.vfo)): trx.txi = 0

        trx.splitmode  = int(x[4])  # split mode
        trx.band       = int(x[5])  # band
        if(trx.band > len(BAND)): trx.band = 0
        trx.mode       = int(x[6])  # mode
        if(trx.mode > len(MODE)): trx.mode = 1
    except:
        pass

def SaveState(trx):
    """save VFO state to file"""
    s = f"{trx.vfofreq[0]} " + f"{trx.vfofreq[1]} " + f"{trx.act} " + f"{trx.txi} " + f"{trx.splitmode} " + f"{trx.band} " + f"{trx.mode}"
    with open(freqfile, "w") as fd:
        fd.write(s)
        fd.close()

########################################################################

def trxOn(pin):
    """GPIO event handler for trx on/off event"""
    sleep(0.05)
    trx = GPIO.input(TRX_ON)
    if(trx):
        ResetDDS()
        LoadState(dds)
        SetDDS(dds.act, dds.vfofreq[dds.act])
        LockDDS(dds)
    else:
        SaveState(dds)
##    print("ON:", trx, dds.vfofreq[dds.act])

def txOnOff(pin):
    """GPIO event handler for tx on/off event"""
    sleep(0.05)
    dds.txon = GPIO.input(TX_ON)
##    print("TX:", dds.txon, dds.ptt)
    if(dds.ptt == 0 and dds.splitmode == 1): # excute on ext ptt change only
        if(dds.txon): i = dds.txi
        else:         i = dds.act
        ChgDDS(i, dds.vfofreq[i])
    LockDDS(dds)

def cwOnOff(pin):
    """GPIO event handler for tx cw on/off event"""
    sleep(0.05)
    cw = GPIO.input(TX_CW)
    if(dds.splitmode == 0):
        if(cw and dds.cwshift == 0):
            i = dds.act
            dds.vfofreq[i] += CW_TXSHIFT
            dds.cwshift = 1
            SetDDS(i, dds.vfofreq[i])
            LockDDS(dds)
        elif(cw == 0 and dds.cwshift):
            i = dds.act
            dds.vfofreq[i] -= CW_TXSHIFT
            dds.cwshift = 0
            SetDDS(i, dds.vfofreq[i])
            LockDDS(dds)

########################################################################
#
# handling of IR/key events

def OnNumKey(trx, num):
    """handle digits from remote control, 3-digits for new khz value"""
    if(trx.digidx == 0):
        trx.dignum = num * 100 
        trx.digidx = 1
    elif(trx.digidx == 1):
        trx.dignum += num * 10 
        trx.digidx = 2
    else:
        trx.dignum += num 
        # map 500-999 to 0-499
        if(trx.dignum > 500): trx.dignum -= 500
        trx.last = trx.vfofreq[trx.act]
        trx.vfofreq[trx.act] = trx.dignum * 1000 + FREQ_MIN
        trx.digidx = -1

def ChgFreq(trx, value):
    """change frequency up or down"""
    i = trx.act
    trx.vfofreq[i] += value
    if(trx.vfofreq[i] > FREQ_MAX): trx.vfofreq[i] = FREQ_MAX
    if(trx.vfofreq[i] < FREQ_MIN): trx.vfofreq[i] = FREQ_MIN

def MemFreq(trx, n):
    """save or load frequency to/from memory"""
    if(trx.save == 1):
        trx.memfreq[n] = trx.vfofreq[trx.act]
    else:
        trx.last = trx.vfofreq[trx.act]
        trx.vfofreq[trx.act] = trx.memfreq[n]

def UpdateBand(trx, x, i):
     """change band index based on frequency"""
     x = x - trx.vfofreq[i] + FREQ_MIN
     try:
         trx.band = BAND.index(x)
     except:
         pass

def OnKey(trx, key):
    """handler for input keys (lirc is used for IR input)"""
    if(trx.save > 0): trx.save -= 1         # decrement save flag

    # A sequence of 3 numeric keys sets a new kHz frequency value
    # any other key resets the 3 digit counter

    if(key == ecodes.KEY_0): OnNumKey(trx, 0)
    elif(key == ecodes.KEY_1): OnNumKey(trx, 1)
    elif(key == ecodes.KEY_2): OnNumKey(trx, 2)
    elif(key == ecodes.KEY_3): OnNumKey(trx, 3)
    elif(key == ecodes.KEY_4): OnNumKey(trx, 4)
    elif(key == ecodes.KEY_5): OnNumKey(trx, 5)
    elif(key == ecodes.KEY_6): OnNumKey(trx, 6)
    elif(key == ecodes.KEY_7): OnNumKey(trx, 7)
    elif(key == ecodes.KEY_8): OnNumKey(trx, 8)
    elif(key == ecodes.KEY_9): OnNumKey(trx, 9)
    else:
        # reset digit counter on ALL non Num keys
        trx.digidx = 0

    # numeric input >0 in progress, < 0 complete
    if(trx.digidx > 0): return
    elif(trx.digidx < 0): trx.digidx = 0

    # Colored keys on remote control are used for freq memory
    # menu  + <X> key stores the frequency, <X> key loads frequency
    # pressing other keys cancel a store frequency operation
    # start new 2 key sequnce, use MENU+MENU to cancel
    # only if last key was menu key trx.save == 1 else 0
    elif(key == ecodes.KEY_MENU):
        if(trx.save == 0): trx.save = 2
    elif(key == ecodes.KEY_TITLE): MemFreq(trx, 0)
    elif(key == ecodes.KEY_SUBTITLE): MemFreq(trx, 1)
    elif(key == ecodes.KEY_INFO): MemFreq(trx, 2)
    elif(key == ecodes.KEY_EPG): MemFreq(trx, 3)
    elif(key == ecodes.KEY_TEXT): MemFreq(trx, 4)

    # UP/DOWN key: frequency up or down depending on actual step size
    elif(key == ecodes.KEY_UP): ChgFreq(trx, STEPS[trx.step])
    elif(key == ecodes.KEY_DOWN): ChgFreq(trx, -STEPS[trx.step])

    # LEFT/RIGHT key, LEFT/RIGHT mouse button: select step size
    elif(key == ecodes.KEY_RIGHT or key == ecodes.BTN_LEFT):
        trx.step += 1
        if(trx.step >= len(STEPS)): trx.step = len(STEPS) -1
        LockDDS(trx)
        return
    elif(key == ecodes.KEY_LEFT or key == ecodes.BTN_RIGHT):
        trx.step -= 1
        if(trx.step < 1): trx.step = 1
        LockDDS(trx)
        return

    # CHANNELUP/CHANNELDOWN key:
    # frequncy up or down by STEPS[0] value)
    elif(key == ecodes.KEY_CHANNELUP): ChgFreq(trx, STEPS[0])
    elif(key == ecodes.KEY_CHANNELDOWN): ChgFreq(trx, -STEPS[0])

    # BACK key: back to previous frequncy
    elif(key == ecodes.KEY_BACK):
        tmp = trx.vfofreq[trx.act]
        trx.vfofreq[trx.act] = trx.last
        trx.last = tmp

    # FORWARD/REWIND key: select band
    elif(key == ecodes.KEY_FORWARD):
        trx.band += 1
        if(trx.band >= len(BAND)): trx.band = 0
        LockDDS(trx)
        return
    elif(key == ecodes.KEY_REWIND):
        trx.band -= 1
        if(trx.band < 0): trx.band = len(BAND) - 1
        LockDDS(trx)
        return

    # NEXT/LAST key: select mode
    elif(key == ecodes.KEY_NEXT):
        trx.mode += 1
        if(trx.mode >= len(MODE)): trx.mode = 0
        LockDDS(trx)
        return
    elif(key == ecodes.KEY_LAST):
        trx.mode -= 1
        if(trx.mode < 0): trx.mode = len(MODE) - 1
        LockDDS(trx)
        return

    # OK key, middle mouse button: toggle VFO
    elif(key == ecodes.KEY_OK or key == ecodes.BTN_MIDDLE):
        trx.last = trx.vfofreq[trx.act]
        trx.act += 1
        if(trx.act >= len(trx.vfo)): trx.act = 0
        if(trx.splitmode == 1):
            trx.txi = trx.act + 1
            if(trx.txi >= len(trx.vfo)): trx.txi = 0
        ChgDDS(trx.act, trx.vfofreq[trx.act])
        LockDDS(trx)
        return

    # RECORD key: toggle ptt
    elif(key == ecodes.KEY_RECORD):
        trx.ptt = not trx.ptt
        trx.txon = trx.ptt
        if(trx.splitmode == 1):
            if(trx.txon): i = trx.txi
            else:         i = trx.act
            ChgDDS(i, trx.vfofreq[i])
        LockDDS(trx)
        GPIO.output(PTT, trx.ptt)
        return

    # PAUSE key: toggle split mode
    # MENU+PAUSE key sequence: sets tx vfo freq to rx vfo freq
    elif(key == ecodes.KEY_PAUSE):
        trx.splitmode = (trx.splitmode + 1) & 1
        trx.txi = trx.act + 1
        if(trx.txi >= len(trx.vfo)): trx.txi = 0
        if(trx.save == 1 and trx.splitmode == 1):
            trx.vfofreq[trx.txi] = trx.vfofreq[trx.act]
        LockDDS(trx)
        return

    # MENU+TUNER key sequence: terminate this VFO program
    # TUNER key is used by lirc to start this VFO program
    elif(key == ecodes.KEY_TUNER):
        sleep(0.5)
        if(trx.save == 1): sighandler(0,0)

    else:
        print("return")
        return

    if(trx.save): return      # no update required on 2 key sequence

    # update DDS with new VFO frequency
    SetDDS(trx.act, trx.vfofreq[trx.act])
    LockDDS(trx)

def OnWheel(trx, value):
    ChgFreq(trx, value * STEPS[trx.step])
    SetDDS(trx.act, trx.vfofreq[trx.act])
    LockDDS(trx)

def sighandler(signum, frame):
    SaveState(dds)
    GPIO.remove_event_detect(TX_ON)
    GPIO.cleanup()
    try:
        os.remove(lockfile)
    except:
        pass
    raise SystemExit
  
########################################################################
#
# handling of requests from hamlib clients

class HamlibHandler:
  """This class is created for each connection to the server.  It services requests from each client"""
  SingleLetters = {       # convert single-letter commands to long commands
    'f':'freq',
    'm':'mode',
    't':'ptt',
    'v':'vfo',
    's':'split_vfo',
    'i':'split_freq',
    'x':'split_mode'
    }

  def __init__(self, app, sock, address):
    self.app = app                # Reference back to the "hardware"
    self.sock = sock
    self.address = address
    sock.settimeout(0.0)
    self.received = b''
    h = self.Handlers = {}
    h[''] = self.ErrProtocol
    h['chk_vfo']         = self.ChkVfo
    h['dump_state']      = self.DumpState
    h['get_freq']        = self.GetFreq
    h['set_freq']        = self.SetFreq
    h['get_mode']        = self.GetMode
    h['set_mode']        = self.SetMode
    h['get_vfo']         = self.GetVfo
    h['set_vfo']         = self.SetVfo
    h['get_ptt']         = self.GetPtt
    h['set_ptt']         = self.SetPtt
    h['get_split_vfo']   = self.GetSplitVfo
    h['set_split_vfo']   = self.SetSplitVfo
    h['get_split_freq']  = self.GetSplitFreq
    h['set_split_freq']  = self.SetSplitFreq
    h['get_split_mode']  = self.GetSplitMode
    h['set_split_mode']  = self.SetSplitMode

  def Send(self, text):
    """Send text back to the client."""
    try:
      self.sock.sendall(bytearray(text.encode()))
    except socket.error:
      self.sock.close()
      self.sock = None
  
  def Reply(self, *args):       # args is name, value, name, value, ..., int
    """Create a string reply of name, value pairs, and an ending integer code."""
    if self.extended:           # Use extended format
      t = "%s:" % self.cmd      # Extended format echoes command and parameters
      for param in self.params:
        t = "%s %s" % (t, param)
      t += self.extended
      for i in range(0, len(args) - 1, 2):
        t = "%s%s: %s%c" % (t, args[i], args[i+1], self.extended)
      t += "RPRT %d\n" % args[-1]
    elif len(args) > 1:         # Use simple format
      t = ''
      for i in range(1, len(args) - 1, 2):
        t = "%s%s\n" % (t, args[i])
    else:                       # No names; just the required integer code
      t = "RPRT %d\n" % args[0]
##    print('Sent:', t)
    self.Send(t)

  def ErrParam(self):           # Invalid parameter
    self.Reply(-1)

  def UnImplemented(self):      # Command not implemented
    self.Reply(-4)

  def ErrProtocol(self):        # Protocol error
    self.Reply(-8)

  def Process(self):
    """This is the main processing loop, and is called frequently.  It reads and satisfies requests."""
    if not self.sock:
      return 0
    try:        # Read any data from the socket
      text = self.sock.recv(1024)
    except socket.timeout:                # This does not work
      pass
    except socket.error:                  # Nothing to read
      pass
    else:                                 # We got some characters
      self.received += text
    if b'\n' in self.received:
      # complete command with newline is available
      # Split off the command, save any further characters
      cmd, self.received = self.received.split(b'\n', 1)
    else:
      return 1
    try:
        cmd = cmd.decode()
        cmd = cmd.strip()                 # Here is our command
##        print('Rcvd:', cmd)
    except:
        cmd = False

    if not cmd:                           # ??? Indicates a closed connection?
##      print('empty command')
      self.sock.close()
      self.sock = None
      return 0
    # Parse the command and call the appropriate handler
    if cmd[0] == '+':                     # rigctld Extended Response Protocol
      self.extended = '\n'
      cmd = cmd[1:].strip()
    elif cmd[0] in ';|,':                 # rigctld Extended Response Protocol
      self.extended = cmd[0]
      cmd = cmd[1:].strip()
    else:
      self.extended = None
    if cmd[0:1] == '\\':            # long form command starting with backslash
      args = cmd[1:].split()
      self.cmd = args[0]
      self.params = args[1:]
      self.Handlers.get(self.cmd, self.UnImplemented)()
    else:                                 # single-letter command
      self.params = cmd[1:].strip()
      cmd = cmd[0:1]
      try:
        t = self.SingleLetters[cmd.lower()]
      except KeyError:
        self.UnImplemented()
      else:
        if cmd in string.ascii_uppercase:
          self.cmd = 'set_' + t
        else:
          self.cmd = 'get_' + t
        self.Handlers.get(self.cmd, self.UnImplemented)()
    return 1

# ---------------------------------------
# These are the handlers for each request

  def ChkVfo(self):
    self.Reply('ChkVFO', 0, 0)

  def DumpState(self):
# prot_version\nrig_model\n0\n
    s = "0\n2\n\0\n"
#rxstartf rxendf rxmodes rxlow_power rxhigh_power rxvfo rxant\n
    s += "3500000.000000 4000000.000000 0x4 -1 -1 0x10000003 0x3\n"
    s += "7000000.000000 7500000.000000 0x4 -1 -1 0x10000003 0x3\n"
    s += "14000000.000000 14500000.000000 0x4 -1 -1 0x10000003 0x3\n"
    s += "21000000.000000 21500000.000000 0x4 -1 -1 0x10000003 0x3\n"
    s += "28000000.000000 30000000.000000 0x4 -1 -1 0x10000003 0x3\n"
    s += "0 0 0 0 0 0 0\n"
#txstartf txendf txmodes txlow_power txhigh_power txvfo txant\n
    s += "3500000.000000 4000000.000000 0x4 -1 -1 0x10000003 0x3\n"
    s += "7000000.000000 7500000.000000 0x4 -1 -1 0x10000003 0x3\n"
    s += "14000000.000000 14500000.000000 0x4 -1 -1 0x10000003 0x3\n"
    s += "21000000.000000 21500000.000000 0x4 -1 -1 0x10000003 0x3\n"
    s += "28000000.000000 30000000.000000 0x4 -1 -1 0x10000003 0x3\n"
    s += "0 0 0 0 0 0 0\n"
#modes tuningsteps
    s += "0 0\n"
#modes bandwidth
    s += "0 0\n"
#max_rit\nmax_xit\nmax_ifshift\nannounces\n
#preamp1 ...\n
#attenuator1 ...\n
#has_get_func\nhas_set_func\nhas_get_level\nhas_set_level\n
#has_get_parm\nhas_set_parm\n
    s += "0\n0\n0\n\0\n\0\n\0\n0\n0\n0\n\0\n\0\n\0\n"
    self.Send(s)

  def GetFreq(self):
    self.Reply('Frequency', self.app.freq[self.app.act], 0)

  def SetFreq(self):
    try:
      x = float(self.params)
      self.Reply(0)
    except:
      self.ErrParam()
    else:
      x = int(x + 0.5)
      i = self.app.act
      self.app.vfofreq[i] = (x % 500000) + FREQ_MIN
      SetDDS(i, self.app.vfofreq[i])
      UpdateBand(self.app, x, i)
      LockDDS(self.app)

  def GetMode(self):
    self.Reply('Mode', MODE[self.app.mode], 'Passband', self.app.bandwidth, 0)

  def SetMode(self):
    try:
      mode, bw = self.params.split()
      bw = int(float(bw) + 0.5)
      self.Reply(0)
    except:
      self.ErrParam()
#    else:
# cannot change
#      self.app.mode      = mode
#      self.app.bandwidth = bw

  def GetVfo(self):
    self.Reply('VFO', self.app.vfo[self.app.act], 0)

  def SetVfo(self):
    try:
      x = self.params.upper()
      self.Reply(0)
    except:
      self.ErrParam()
    else:
      try:
        i = self.app.vfo.index(x)
        self.app.act = i
        SetDDS(i, self.app.vfofreq[i])
        LockDDS(self.app)
      except:
        pass

  def GetPtt(self):
    self.Reply('PTT', self.app.ptt, 0) # returns ptt state

  def SetPtt(self):
    try:
      x = int(self.params)
      self.Reply(0)
    except:
      self.ErrParam()
    else:
      if x: self.app.ptt = 1
      else: self.app.ptt = 0
      self.app.txon = self.app.ptt
      if(self.app.splitmode == 1):
        if(self.app.txon): i = self.app.txi
        else:              i = self.app.act
        ChgDDS(i, self.app.vfofreq[i])
      LockDDS(self.app)
      GPIO.output(PTT, self.app.ptt)

  # -----------------------------------

  def GetSplitVfo(self):
    self.Reply('SPLIT', self.app.splitmode, 'TXVFO',
               self.app.vfo[self.app.txi], 0)

  def SetSplitVfo(self):
    try:
      splitmode, txvfo = self.params.split()
      self.Reply(0)
    except:
      self.ErrParam()
    else:
      self.app.splitmode = splitmode
      try:
        i = self.app.vfo.index(txvfo)
        self.app.txi = i
      except:
        pass

  def GetSplitFreq(self):
    self.Reply('TX Frequency', self.app.freq[self.app.txi], 0)

  def SetSplitFreq(self):
    try:
      x = float(self.params)
      self.Reply(0)
    except:
      self.ErrParam()
    else:
      x = int(x + 0.5)
      i = self.app.txi
      self.app.vfofreq[i] = (x % 500000) + FREQ_MIN
      if(DDSCHIP == 9833 and len(self.app.vfo) == 2):
          SetFreq9833(i, self.app.vfofreq[i])
      # cannot split with an another band, stay on current band
      # UpdateBand(self.app, x, i)
      self.app.freq[i] = self.app.vfofreq[i] - FREQ_MIN + BAND[self.app.band]

  def GetSplitMode(self):
    self.Reply('TX Mode', MODE[self.app.mode], 'TX Passband', self.app.bandwidth, 0)

  def SetSplitMode(self):
    try:
      mode, bw = self.params.split()
      bw = int(float(bw) + 0.5)
      self.Reply(0)
    except:
      self.ErrParam()
#    else:
# cannot change
#      self.app.mode      = mode
#      self.app.bandwidth = bw

########################################################################
#
#   Main Program

if(sys.argv[0] == "9833"): DDSCHIP = 9833

if(exists(lockfile)):        # only one instance
    os.remove(lockfile)      # remove lock file
    raise SystemExit

print('DDS-Modul:', DDSCHIP)

signal.signal(signal.SIGTERM, sighandler)
signal.signal(signal.SIGQUIT, sighandler)
signal.signal(signal.SIGHUP, sighandler)
signal.signal(signal.SIGINT, sighandler)

InitIO()                     # init GPIO
dds = DDS().Init()           # load configuration
ResetDDS()
LoadState(dds)
SetDDS(dds.act, dds.vfofreq[dds.act])
LockDDS(dds)                 # create lock file

# connect event handler for TRX line on GPIO.input 
GPIO.add_event_detect(TRX_ON, GPIO.BOTH, callback=trxOn, bouncetime=100)

# connect event handler for TX line on GPIO.input 
GPIO.add_event_detect(TX_ON, GPIO.BOTH, callback=txOnOff, bouncetime=100)

# connect event handler for TX_CW line on GPIO.input 
GPIO.add_event_detect(TX_CW, GPIO.BOTH, callback=cwOnOff, bouncetime=100)

# event handling for input devices (remote control, usb keyboard)
devices = [evdev.InputDevice(path) for path in evdev.list_devices()]

##for device in devices: print(device.path, device.name, device.phys, device.fd)

inputs = {dev.fd: dev for dev in devices}

# event handling for remote input (hamlib socket client)
hamlib_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    hamlib_socket.bind(('', PORT))
    hamlib_socket.settimeout(0.0)
    hamlib_socket.listen(0)
    inputs[hamlib_socket] = hamlib_socket
except socket.error:
    print("could not open listening socket")
hamlib_clients = []

try:
    while True:
        fdin, fdout, fderr = select(inputs, [], [])
        for fd in fdin:
            if fd is hamlib_socket:                # new hamlib client
                try:
                    conn, address = fd.accept()
                    print('Connection from', address)
                    conn.setblocking(0)
                    inputs[conn] = conn
                    hamlib_clients.append(HamlibHandler(dds, conn, address))
                except socket.error:
                    pass
            else:
                for client in hamlib_clients:      # check for tcp event
                    ret = client.Process()
                    if not ret:          # False indicates a closed connection
                        hamlib_clients.remove(client)      # remove the client
                        inputs.pop(conn)                   # remove connection
                        conn.close()                       # close connection
                        print('Closed', client.address)
                        break

                for device in devices:             # check for input event
                    if(fd == device.fd):
                        for event in inputs[fd].read():
                            if event.type == ecodes.EV_REL:
##                               print(evdev.categorize(event))
                                if(event.code == ecodes.REL_WHEEL):
                                    OnWheel(dds, event.value)

                            elif event.type == ecodes.EV_KEY:
##                               print(evdev.categorize(event))
                                if(event.value != 0):
                                    OnKey(dds, event.code)

except KeyboardInterrupt:
    sighandler()
