From b6a74c680b8c75d851fac8e8da0a3d32caaa501c Mon Sep 17 00:00:00 2001 From: DJ2LS <75909252+DJ2LS@users.noreply.github.com> Date: Tue, 14 Feb 2023 22:02:30 +0100 Subject: [PATCH] network support for tci --- tnc/modem.py | 13 ++- tnc/rigctld.py | 2 +- tnc/tci.py | 216 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 226 insertions(+), 5 deletions(-) create mode 100644 tnc/tci.py diff --git a/tnc/modem.py b/tnc/modem.py index 1e88f387..65471812 100644 --- a/tnc/modem.py +++ b/tnc/modem.py @@ -25,6 +25,7 @@ import sounddevice as sd import static import structlog import ujson as json +import tci from queues import DATA_QUEUE_RECEIVED, MODEM_RECEIVED_QUEUE, MODEM_TRANSMIT_QUEUE, RIGCTLD_COMMAND_QUEUE TESTMODE = False @@ -184,9 +185,14 @@ class RF: class Object: """An object for simulating audio stream""" active = True - self.stream = Object() + # lets init TCI module + self.tci_module = tci.TCI() + + # lets open TCI radio + self.tci_module.open_rig(static.TCI_IP, static.TCI_PORT) + # let's start the audio rx callback self.log.debug("[MDM] Starting tci rx callback thread") tci_rx_callback_thread = threading.Thread( @@ -325,6 +331,7 @@ class RF: # -----write if len(self.modoutqueue) > 0 and not self.mod_out_locked: data_out48k = self.modoutqueue.popleft() + self.tci_module.push_audio(data_out48k) def tci_rx_callback(self) -> None: @@ -338,9 +345,7 @@ class RF: while True: threading.Event().wait(0.01) - # generate random audio data - data_in48k = np.random.uniform(-1, 1, 48000) - + data_in48k = self.tci_module.get_audio() x = np.frombuffer(data_in48k, dtype=np.int16) x = self.resampler.resample48_to_8(x) diff --git a/tnc/rigctld.py b/tnc/rigctld.py index 6d1a316c..6613379d 100644 --- a/tnc/rigctld.py +++ b/tnc/rigctld.py @@ -182,7 +182,7 @@ class radio: while recv: try: - data = self.data_connection.recv(64) + data = self.data_connection.recv(4800) except socket.timeout: recv = False diff --git a/tnc/tci.py b/tnc/tci.py new file mode 100644 index 00000000..459bf49f --- /dev/null +++ b/tnc/tci.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +# class taken from darksidelemm +# rigctl - https://github.com/darksidelemm/rotctld-web-gui/blob/master/rotatorgui.py#L35 +# +# modified and adjusted to FreeDATA needs by DJ2LS + +import socket +import structlog +import threading +import static +import numpy as np + +class TCI: + """TCI (hamlib) communication class""" + + log = structlog.get_logger("radio (TCI)") + + def __init__(self, hostname="localhost", port=9000, poll_rate=5, timeout=5): + """Open a connection to TCI, and test it for validity""" + self.ptt_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.data_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.ptt_connected = False + self.data_connected = False + self.hostname = hostname + self.port = port + self.connection_attempts = 5 + + # class wide variable for some parameters + self.bandwidth = '' + self.frequency = '' + self.mode = '' + self.alc = '' + self.strength = '' + self.rf = '' + + def open_rig( + self, + tci_ip, + tci_port + ): + """ + + Args: + tci_ip: + tci_port: + + Returns: + + """ + self.hostname = tci_ip + self.port = int(tci_port) + + # _ptt_connect = self.ptt_connect() + # _data_connect = self.data_connect() + + ptt_thread = threading.Thread(target=self.ptt_connect, args=[], daemon=True) + ptt_thread.start() + + data_thread = threading.Thread(target=self.data_connect, args=[], daemon=True) + data_thread.start() + + # wait some time + threading.Event().wait(0.5) + + if self.ptt_connected and self.data_connected: + self.log.debug("Rigctl DATA/PTT initialized") + return True + + self.log.error( + "[TCI] Can't connect!", ip=self.hostname, port=self.port + ) + return False + + def ptt_connect(self): + """Connect to TCI instance""" + while True: + + if not self.ptt_connected: + try: + self.ptt_connection = socket.create_connection((self.hostname, self.port)) + self.ptt_connected = True + self.log.info( + "[TCI] Connected PTT instance to TCI!", ip=self.hostname, port=self.port + ) + except Exception as err: + # ConnectionRefusedError: [Errno 111] Connection refused + self.close_rig() + self.log.warning( + "[TCI] PTT Reconnect...", + ip=self.hostname, + port=self.port, + e=err, + ) + + threading.Event().wait(0.5) + + def data_connect(self): + """Connect to TCI instance""" + while True: + if not self.data_connected: + try: + self.data_connection = socket.create_connection((self.hostname, self.port)) + self.data_connected = True + self.log.info( + "[TCI] Connected DATA instance to TCI!", ip=self.hostname, port=self.port + ) + except Exception as err: + # ConnectionRefusedError: [Errno 111] Connection refused + self.close_rig() + self.log.warning( + "[TCI] DATA Reconnect...", + ip=self.hostname, + port=self.port, + e=err, + ) + threading.Event().wait(0.5) + + def close_rig(self): + """ """ + self.ptt_sock.close() + self.data_sock.close() + self.ptt_connected = False + self.data_connected = False + + def send_ptt_command(self, command, expect_answer) -> bytes: + """Send a command to the connected rotctld instance, + and return the return value. + + Args: + command: + + """ + if self.ptt_connected: + try: + self.ptt_connection.sendall(command + b"\n") + except Exception: + self.log.warning( + "[TCI] Command not executed!", + command=command, + ip=self.hostname, + port=self.port, + ) + self.ptt_connected = False + return b"" + + def send_data_command(self, command, expect_answer) -> bytes: + """Send a command to the connected tci instance, + and return the return value. + + Args: + command: + + """ + if self.data_connected: + self.data_connection.setblocking(False) + self.data_connection.settimeout(0.05) + try: + self.data_connection.sendall(command + b"\n") + + + except Exception: + self.log.warning( + "[TCI] Command not executed!", + command=command, + ip=self.hostname, + port=self.port, + ) + self.data_connected = False + + try: + # recv seems to be blocking so in case of ptt we don't need the response + # maybe this speeds things up and avoids blocking states + recv = True + data = b'' + + while recv: + try: + + data = self.data_connection.recv(64) + + except socket.timeout: + recv = False + + return data + + # return self.data_connection.recv(64) if expect_answer else True + except Exception: + self.log.warning( + "[TCI] No command response!", + command=command, + ip=self.hostname, + port=self.port, + ) + self.data_connected = False + return b"" + def get_audio(self): + """""" + # generate random audio data + if not self.data_connected: + return np.random.uniform(-1, 1, 48000) + + try: + return self.send_data_command(b"GET AUDIO COMMAND", True) + except Exception: + print("NOOOOO") + return False + + + def push_audio(self): + """ """ + try: + return self.send_data_command(b"PUSH AUDIO COMMAND ", True) + except Exception: + return False +