# This is a hardware file for control of a rig using the Hamlib rigctld daemon.
# This hardware will not start the daemon rigctld, so you must start it yourself.
# This hardware will connect to rigctld, and rigctld connects to the rig.  You can test
# rigctld with the command rigctl. See the hamlib documentation for rigctld and rigctl.
#
# 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
from __future__ import absolute_import

DEBUG = 0

import socket, time, traceback
import _quisk as QS

from quisk_hardware_model import Hardware as BaseHardware

class Hardware(BaseHardware):
  def __init__(self, app, conf):
    BaseHardware.__init__(self, app, conf)
    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):
    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
    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 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:	# Patch thanks to Rolandas, LY0NAS
      self.hamlib_socket.sendall(text.encode('utf-8', errors='ignore'))
    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).decode('utf-8', errors='replace')
    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.modeButns.SetLabel(mode, True)		# Set mode
        else:
          if DEBUG: print('Unknown', reply)
      except:
        if DEBUG: traceback.print_exc()
