Monday, January 22, 2018

SDR-2018: Minor success!

If you've been following this project, you know that I've been "stuck" on a software issue (I can almost hear Bill snickering) and haven't made any progress for several weeks.  Well, I've finally found that there is indeed light at the end of the tunnel, and it's not necessarily an oncoming train!

The problem is due entirely to my unfamiliarity with the Python language, and without that knowledge base, editing the Quisk hardware file for my application has been hit and miss, with more misses than hits.  To overcome this, I've been giving myself a crash-course on Python, and it's starting to pay-off.

Specifically, where I've been stuck is in sending/receiving a PTT signal between Quisk and my hardware, which emulates a Yaesu FT857D.  Quisk shares frequency and mode information over the CAT link just fine, but, in an exchange with Jim, N2ADR, I learned that Quisk doesn't poll hamlib for the PTT signal - that has to be asserted by the remote device.  I suppose that I could have rewritten the Arduino code to do that, but I got to thinking about all those unused GPIO lines on the Pi, which seemed almost made to order for this sort of thing.  But, without adequate Python knowledge, my efforts were futile.

After spending a few weeks boning-up on Python, tonight I had a fleeting moment of lucidity and started to understand what I needed to do to my Quisk hardware file in order to get it to set a GPIO pin HIGH when Quisk's on-screen PTT button is active.  Eureka! 

I've still got to write the code to allow Quisk to respond to a PTT signal coming from my hardware, but I think I know how to do that now and will hit it again tomorrow.

For those who are curious or have masochistic tendencies, here's my current draft hardware file:

# This is a hardware file to test reading and writing PTT status from/to GPIO.
#
# Version (test001) creates a key threading class that is called from the
# OnButtonPTT function to write "keyed" or "dekeyed" to the terminal
# depending on the current status of the PTT screen button.
# Version test002 implements writing PTT OUTPUT to GPIO
#
# It also uses the hamlib library to communicate with the radio hardware:
# If you change the frequency in Quisk, the change is sent to rigctld.  This hardware
# will query (poll) rigctld for its frequency at intervals to see if the rig changed
# the frequency.  If it did, the change is sent to Quisk.
#
# These are the attributes we watch:  Rx frequency, mode

from __future__ import print_function

DEBUG = 0


import socket, time, traceback
import _quisk as QS
import RPi.GPIO as GPIO

from quisk_hardware_model import Hardware as BaseHardware



class Hardware(BaseHardware):
  def __init__(self, app, conf):
    BaseHardware.__init__(self, app, conf)

# assign "keythread" to represent Key_Thread class:
    self.keythread = Key_Thread()
    self.hamlib_rigctld_port = 4532        # Standard rigctld control port
    self.hamlib_poll_seconds = 0.2        # Time interval to poll for changes
    self.hamlib_connected = False
    self.radio_freq = None
    self.radio_mode = None
    self.quisk_freq = None
    self.quisk_vfo = None
    self.quisk_mode = 'USB'
    self.received = ''
    self.toggle = False
    self.time0 = 0
  def open(self):
    ret = BaseHardware.open(self)
    self.hamlib_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self.hamlib_socket.settimeout(0.0)
    self.ConnectRigctld()
    return ret
  def close(self):
    #Kill keythread and close GPIO:

    if self.keythread:
        self.keythread.stop()
    self.hamlib_socket.close()
    self.hamlib_connected = False
    return BaseHardware.close(self)
  def ConnectRigctld(self):
    if self.hamlib_connected:
      return True
    try:
      self.hamlib_socket.connect(('localhost', self.hamlib_rigctld_port))
    except:
      return False      # Failure to connect
    self.hamlib_connected = True
    if DEBUG: print("rigctld connected")
    return True         # Success
  def ChangeFrequency(self, tune, vfo, source='', band='', event=None):
    self.quisk_freq = tune
    self.quisk_vfo = tune -5000
    if DEBUG: print('Change', source, tune)
    return self.quisk_freq, self.quisk_vfo
  def ReturnFrequency(self):
    # Return the current tuning and VFO frequency.  If neither have changed,
    # you can return (None, None).  This is called at about 10 Hz by the main.

    return self.quisk_freq, self.quisk_vfo
  def ChangeMode(self, mode):        # Change the tx/rx mode
    # mode is a string: "USB", "AM", etc.
    if mode == 'CWU':
      mode = 'CW'
    elif mode == 'CWL':
      mode = 'CW'
    elif mode[0:4] == 'DGT-':
      mode = 'USB'
    self.quisk_mode = mode
    if DEBUG: print('Change', mode)
  def OnButtonPTT(self, event):
    btn = event.GetEventObject()
    if btn.GetValue():
      QS.set_PTT(1)
# Execute "keyed" function from Key_Thread class:
      self.keythread.keyed()
    else:
      QS.set_PTT(0)
# Execute "dekeyed" function from Key_Thread class:
      self.keythread.dekeyed()
  def ChangeBand(self, band):
    # band is a string: "60", "40", "WWV", etc.
    pass
  def HeartBeat(self):    # Called at about 10 Hz by the main
    if not self.hamlib_connected:    # Continually try to connect
      try:
        self.hamlib_socket.connect(('localhost', self.hamlib_rigctld_port))
      except:
        return
      else:
        self.hamlib_connected = True
        if DEBUG: print("rigctld Connected")
    self.ReadHamlib()
    if time.time() - self.time0 < self.hamlib_poll_seconds:
      return
    self.time0 = time.time()
    if self.quisk_mode != self.radio_mode:
      self.HamlibSend("|M %s 0\n" % self.quisk_mode)
    elif self.quisk_freq != self.radio_freq:
      self.HamlibSend("|F %d\n" % self.quisk_freq)
    elif self.toggle:
      self.toggle = False
      self.HamlibSend("|f\n")        # Poll for frequency
    else:
      self.toggle = True
      self.HamlibSend("|m\n")        # Poll for mode
  def HamlibSend(self, text):
    if DEBUG: print('Send', text, end=' ')
    try:
      self.hamlib_socket.sendall(text)
    except socket.error:
      pass
  def ReadHamlib(self):
    if not self.hamlib_connected:
      return
    try:    # Read any data from the socket
      text = self.hamlib_socket.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
    while '\n' in self.received:    # A complete response ending with newline is available
      reply, self.received = self.received.split('\n', 1)    # Split off the reply, save any further characters
      reply = reply.strip()        # Here is our reply
      if reply[-6:] != 'RPRT 0':
        if DEBUG: print('Reject', reply)
        continue
      try:
        if reply[0:9] == 'set_freq:':        # set_freq: 18120472|RPRT 0
          freq, status = reply[9:].split('|')
          freq = int(freq)
          if DEBUG: print('  Radio S freq', freq)
          self.radio_freq = freq
        elif reply[0:9] == 'get_freq:':    # get_freq:|Frequency: 18120450|RPRT 0
          z, freq, status = reply.split('|')
          z, freq = freq.split(':')
          freq = int(freq)
          if DEBUG: print('    Radio G freq', freq)
          if self.quisk_freq == self.radio_freq:
            self.radio_freq = freq
            self.ChangeFrequency(freq, self.quisk_vfo, 'hamlib')
        elif reply[0:9] == 'set_mode:':    # set_mode: FM 0|RPRT 0
          mode, status = reply[9:].split('|')
          mode, z = mode.split()
          if DEBUG: print('  Radio S mode', mode)
          self.radio_mode = mode
        elif reply[0:9] == 'get_mode:':    # get_mode:|Mode: FM|Passband: 12000|RPRT 0
          z, mode, passb, status = reply.split('|')
          z, mode = mode.split()
          if DEBUG: print('    Radio G mode', mode)
          if self.quisk_mode == self.radio_mode:
            if self.radio_mode != mode:        # The radio changed the mode
              self.radio_mode = mode
              self.quisk_mode = mode
              if mode in ('CW', 'CWR'):
                mode = 'CWU'
              self.application.OnBtnMode(None, mode)        # Set mode
        else:
          if DEBUG: print('Unknown', reply)
      except:
        if DEBUG: traceback.print_exc()

class Key_Thread:
    def __init__(self):
        GPIO.setmode(GPIO.BOARD)
        GPIO.setup(10,GPIO.OUT)

    def keyed(self):
        #print("Keyed")
        GPIO.output(10, GPIO.HIGH)
    def dekeyed(self):
        #print("De-Keyed")
        GPIO.output(10, GPIO.LOW)
    def stop(self):
        #Enter code to reset GPIO here:
        print("Key_Thread Stopped")
        GPIO.cleanup()

No comments:

Post a Comment