From 6eafb081a1da63173c1e3dea8737b589719a0861 Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Sun, 22 May 2022 12:38:56 -0400 Subject: [PATCH 01/26] Refactor audio demodulation routines. Reformatted file & sorted imports per PEP8. --- tnc/modem.py | 781 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 508 insertions(+), 273 deletions(-) diff --git a/tnc/modem.py b/tnc/modem.py index cbd24361..9211ac66 100644 --- a/tnc/modem.py +++ b/tnc/modem.py @@ -10,32 +10,26 @@ Created on Wed Dec 23 07:04:24 2020 import atexit import ctypes -import logging import os -import pathlib import queue -import re import sys import threading import time from collections import deque +from typing import Union +import codec2 +import data_handler import numpy as np +import sock import sounddevice as sd +import static import structlog import ujson as json -import audio -import codec2 -import data_handler -import helpers -import log_handler -import sock -import static - TESTMODE = False -RXCHANNEL = '' -TXCHANNEL = '' +RXCHANNEL = "" +TXCHANNEL = "" # Initialize FIFO queue to store received frames MODEM_RECEIVED_QUEUE = queue.Queue() @@ -47,8 +41,10 @@ RECEIVE_DATAC1 = False RECEIVE_DATAC3 = False RECEIVE_FSK_LDPC_1 = False -class RF(): - """ """ + +class RF: + """Class to encapsulate interactions between the audio device and codec2""" + def __init__(self): self.sampler_avg = 0 @@ -58,9 +54,13 @@ class RF(): self.AUDIO_SAMPLE_RATE_TX = 48000 self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 self.AUDIO_FRAMES_PER_BUFFER_RX = 2400 * 2 # 8192 - self.AUDIO_FRAMES_PER_BUFFER_TX = 2400 * 2 # 8192 Lets to some tests with very small chunks for TX - self.AUDIO_CHUNKS = 48 # 8 * (self.AUDIO_SAMPLE_RATE_RX/self.MODEM_SAMPLE_RATE) #48 + # 8192 Let's do some tests with very small chunks for TX + self.AUDIO_FRAMES_PER_BUFFER_TX = 2400 * 2 + + # 8 * (self.AUDIO_SAMPLE_RATE_RX/self.MODEM_SAMPLE_RATE) == 48 + self.AUDIO_CHUNKS = 48 self.AUDIO_CHANNELS = 1 + self.MODE = 0 # Locking state for mod out so buffer will be filled before we can use it # https://github.com/DJ2LS/FreeDATA/issues/127 @@ -68,7 +68,7 @@ class RF(): self.mod_out_locked = True # Make sure our resampler will work - assert (self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 + assert (self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 # type: ignore # Small hack for initializing codec2 via codec2.py module # TODO: Need to change the entire modem module to integrate codec2 module @@ -85,42 +85,101 @@ class RF(): self.fft_data = bytes() # Open codec2 instances - self.datac0_freedv = ctypes.cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), ctypes.c_void_p) - self.c_lib.freedv_set_tuning_range(self.datac0_freedv, ctypes.c_float(static.TUNING_RANGE_FMIN), ctypes.c_float(static.TUNING_RANGE_FMAX)) - self.datac0_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac0_freedv) / 8) - self.datac0_payload_per_frame = self.datac0_bytes_per_frame - 2 - self.datac0_n_nom_modem_samples = self.c_lib.freedv_get_n_nom_modem_samples(self.datac0_freedv) - self.datac0_n_tx_modem_samples = self.c_lib.freedv_get_n_tx_modem_samples(self.datac0_freedv) - self.datac0_n_tx_preamble_modem_samples = self.c_lib.freedv_get_n_tx_preamble_modem_samples(self.datac0_freedv) - self.datac0_n_tx_postamble_modem_samples = self.c_lib.freedv_get_n_tx_postamble_modem_samples(self.datac0_freedv) + # Datac0 - control frames + self.datac0_freedv = ctypes.cast( + codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), ctypes.c_void_p + ) + self.c_lib.freedv_set_tuning_range( + self.datac0_freedv, + ctypes.c_float(static.TUNING_RANGE_FMIN), + ctypes.c_float(static.TUNING_RANGE_FMAX), + ) + self.datac0_bytes_per_frame = int( + codec2.api.freedv_get_bits_per_modem_frame(self.datac0_freedv) / 8 + ) self.datac0_bytes_out = ctypes.create_string_buffer(self.datac0_bytes_per_frame) codec2.api.freedv_set_frames_per_burst(self.datac0_freedv, 1) - self.datac0_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER_RX) + self.datac0_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX) - self.datac1_freedv = ctypes.cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), ctypes.c_void_p) - self.c_lib.freedv_set_tuning_range(self.datac1_freedv, ctypes.c_float(static.TUNING_RANGE_FMIN), ctypes.c_float(static.TUNING_RANGE_FMAX)) - self.datac1_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac1_freedv) / 8) + # Additional Datac0-specific information - these are not referenced anywhere else. + # self.datac0_payload_per_frame = self.datac0_bytes_per_frame - 2 + # self.datac0_n_nom_modem_samples = self.c_lib.freedv_get_n_nom_modem_samples( + # self.datac0_freedv + # ) + # self.datac0_n_tx_modem_samples = self.c_lib.freedv_get_n_tx_modem_samples( + # self.datac0_freedv + # ) + # self.datac0_n_tx_preamble_modem_samples = ( + # self.c_lib.freedv_get_n_tx_preamble_modem_samples(self.datac0_freedv) + # ) + # self.datac0_n_tx_postamble_modem_samples = ( + # self.c_lib.freedv_get_n_tx_postamble_modem_samples(self.datac0_freedv) + # ) + + # Datac1 - higher-bandwidth data frames + self.datac1_freedv = ctypes.cast( + codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), ctypes.c_void_p + ) + self.c_lib.freedv_set_tuning_range( + self.datac1_freedv, + ctypes.c_float(static.TUNING_RANGE_FMIN), + ctypes.c_float(static.TUNING_RANGE_FMAX), + ) + self.datac1_bytes_per_frame = int( + codec2.api.freedv_get_bits_per_modem_frame(self.datac1_freedv) / 8 + ) self.datac1_bytes_out = ctypes.create_string_buffer(self.datac1_bytes_per_frame) codec2.api.freedv_set_frames_per_burst(self.datac1_freedv, 1) - self.datac1_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER_RX) + self.datac1_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX) - self.datac3_freedv = ctypes.cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), ctypes.c_void_p) - self.c_lib.freedv_set_tuning_range(self.datac3_freedv, ctypes.c_float(static.TUNING_RANGE_FMIN), ctypes.c_float(static.TUNING_RANGE_FMAX)) - self.datac3_bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(self.datac3_freedv) / 8) + # Datac3 - lower-bandwidth data frames + self.datac3_freedv = ctypes.cast( + codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), ctypes.c_void_p + ) + self.c_lib.freedv_set_tuning_range( + self.datac3_freedv, + ctypes.c_float(static.TUNING_RANGE_FMIN), + ctypes.c_float(static.TUNING_RANGE_FMAX), + ) + self.datac3_bytes_per_frame = int( + codec2.api.freedv_get_bits_per_modem_frame(self.datac3_freedv) / 8 + ) self.datac3_bytes_out = ctypes.create_string_buffer(self.datac3_bytes_per_frame) codec2.api.freedv_set_frames_per_burst(self.datac3_freedv, 1) - self.datac3_buffer = codec2.audio_buffer(2*self.AUDIO_FRAMES_PER_BUFFER_RX) + self.datac3_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX) - self.fsk_ldpc_freedv_0 = ctypes.cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC, ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV)), ctypes.c_void_p) - self.fsk_ldpc_bytes_per_frame_0 = int(codec2.api.freedv_get_bits_per_modem_frame(self.fsk_ldpc_freedv_0) / 8) - self.fsk_ldpc_bytes_out_0 = ctypes.create_string_buffer(self.fsk_ldpc_bytes_per_frame_0) - #codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, 1) + # FSK Long-distance Parity Code 0 - data frames + self.fsk_ldpc_freedv_0 = ctypes.cast( + codec2.api.freedv_open_advanced( + codec2.api.FREEDV_MODE_FSK_LDPC, + ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV), + ), + ctypes.c_void_p, + ) + self.fsk_ldpc_bytes_per_frame_0 = int( + codec2.api.freedv_get_bits_per_modem_frame(self.fsk_ldpc_freedv_0) / 8 + ) + self.fsk_ldpc_bytes_out_0 = ctypes.create_string_buffer( + self.fsk_ldpc_bytes_per_frame_0 + ) + # codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, 1) self.fsk_ldpc_buffer_0 = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER_RX) - self.fsk_ldpc_freedv_1 = ctypes.cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC, ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV)), ctypes.c_void_p) - self.fsk_ldpc_bytes_per_frame_1 = int(codec2.api.freedv_get_bits_per_modem_frame(self.fsk_ldpc_freedv_1) / 8) - self.fsk_ldpc_bytes_out_1 = ctypes.create_string_buffer(self.fsk_ldpc_bytes_per_frame_1) - #codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, 1) + # FSK Long-distance Parity Code 1 - data frames + self.fsk_ldpc_freedv_1 = ctypes.cast( + codec2.api.freedv_open_advanced( + codec2.api.FREEDV_MODE_FSK_LDPC, + ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV), + ), + ctypes.c_void_p, + ) + self.fsk_ldpc_bytes_per_frame_1 = int( + codec2.api.freedv_get_bits_per_modem_frame(self.fsk_ldpc_freedv_1) / 8 + ) + self.fsk_ldpc_bytes_out_1 = ctypes.create_string_buffer( + self.fsk_ldpc_bytes_per_frame_1 + ) + # codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, 1) self.fsk_ldpc_buffer_1 = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER_RX) # initial nin values @@ -129,99 +188,160 @@ class RF(): self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv) self.fsk_ldpc_nin_0 = codec2.api.freedv_nin(self.fsk_ldpc_freedv_0) self.fsk_ldpc_nin_1 = codec2.api.freedv_nin(self.fsk_ldpc_freedv_1) + # structlog.get_logger("structlog").debug("[MDM] RF: ",datac0_nin=self.datac0_nin) + # --------------------------------------------CREATE PYAUDIO INSTANCE if not TESTMODE: try: - self.stream = sd.RawStream(channels=1, dtype='int16', callback=self.callback, device=(static.AUDIO_INPUT_DEVICE, static.AUDIO_OUTPUT_DEVICE), samplerate = self.AUDIO_SAMPLE_RATE_RX, blocksize=4800) + self.stream = sd.RawStream( + channels=1, + dtype="int16", + callback=self.callback, + device=(static.AUDIO_INPUT_DEVICE, static.AUDIO_OUTPUT_DEVICE), + samplerate=self.AUDIO_SAMPLE_RATE_RX, + blocksize=4800, + ) atexit.register(self.stream.stop) - structlog.get_logger("structlog").info("[MDM] init: opened audio devices") - + structlog.get_logger("structlog").info( + "[MDM] init: opened audio devices" + ) except Exception as e: - structlog.get_logger("structlog").error("[MDM] init: can't open audio device. Exit", e=e) + structlog.get_logger("structlog").error( + "[MDM] init: can't open audio device. Exit", e=e + ) sys.exit(1) try: - structlog.get_logger("structlog").debug("[MDM] init: starting pyaudio callback") - #self.audio_stream.start_stream() + structlog.get_logger("structlog").debug( + "[MDM] init: starting pyaudio callback" + ) + # self.audio_stream.start_stream() self.stream.start() - except Exception as e: - structlog.get_logger("structlog").error("[MDM] init: starting pyaudio callback failed", e=e) - + structlog.get_logger("structlog").error( + "[MDM] init: starting pyaudio callback failed", e=e + ) else: - # create a stream object for simulating audio stream - class Object(object): - pass + + class Object: + """An object for simulating audio stream""" + + active = True + self.stream = Object() self.stream.active = True - # create mkfifo buffer + # Create mkfifo buffers try: os.mkfifo(RXCHANNEL) os.mkfifo(TXCHANNEL) except Exception as e: - structlog.get_logger("structlog").error(f"[MDM] init:mkfifo: Exception: {e}") - pass + structlog.get_logger("structlog").error( + f"[MDM] init:mkfifo: Exception: {e}" + ) - mkfifo_write_callback_thread = threading.Thread(target=self.mkfifo_write_callback, name="MKFIFO WRITE CALLBACK THREAD",daemon=True) + mkfifo_write_callback_thread = threading.Thread( + target=self.mkfifo_write_callback, + name="MKFIFO WRITE CALLBACK THREAD", + daemon=True, + ) mkfifo_write_callback_thread.start() - mkfifo_read_callback_thread = threading.Thread(target=self.mkfifo_read_callback, name="MKFIFO READ CALLBACK THREAD",daemon=True) + structlog.get_logger("structlog").debug( + "[MDM] Starting mkfifo_read_callback" + ) + mkfifo_read_callback_thread = threading.Thread( + target=self.mkfifo_read_callback, + name="MKFIFO READ CALLBACK THREAD", + daemon=True, + ) mkfifo_read_callback_thread.start() # --------------------------------------------INIT AND OPEN HAMLIB # Check how we want to control the radio - if static.HAMLIB_RADIOCONTROL == 'direct': + if static.HAMLIB_RADIOCONTROL == "direct": import rig - elif static.HAMLIB_RADIOCONTROL == 'rigctl': + elif static.HAMLIB_RADIOCONTROL == "rigctl": import rigctl as rig - elif static.HAMLIB_RADIOCONTROL == 'rigctld': + elif static.HAMLIB_RADIOCONTROL == "rigctld": import rigctld as rig - elif static.HAMLIB_RADIOCONTROL == 'disabled': - import rigdummy as rig else: import rigdummy as rig self.hamlib = rig.radio() - self.hamlib.open_rig(devicename=static.HAMLIB_DEVICE_NAME, deviceport=static.HAMLIB_DEVICE_PORT, hamlib_ptt_type=static.HAMLIB_PTT_TYPE, serialspeed=static.HAMLIB_SERIAL_SPEED, pttport=static.HAMLIB_PTT_PORT, data_bits=static.HAMLIB_DATA_BITS, stop_bits=static.HAMLIB_STOP_BITS, handshake=static.HAMLIB_HANDSHAKE, rigctld_ip = static.HAMLIB_RIGCTLD_IP, rigctld_port = static.HAMLIB_RIGCTLD_PORT) + self.hamlib.open_rig( + devicename=static.HAMLIB_DEVICE_NAME, + deviceport=static.HAMLIB_DEVICE_PORT, + hamlib_ptt_type=static.HAMLIB_PTT_TYPE, + serialspeed=static.HAMLIB_SERIAL_SPEED, + pttport=static.HAMLIB_PTT_PORT, + data_bits=static.HAMLIB_DATA_BITS, + stop_bits=static.HAMLIB_STOP_BITS, + handshake=static.HAMLIB_HANDSHAKE, + rigctld_ip=static.HAMLIB_RIGCTLD_IP, + rigctld_port=static.HAMLIB_RIGCTLD_PORT, + ) # --------------------------------------------START DECODER THREAD if static.ENABLE_FFT: - fft_thread = threading.Thread(target=self.calculate_fft, name="FFT_THREAD", daemon=True) + fft_thread = threading.Thread( + target=self.calculate_fft, name="FFT_THREAD", daemon=True + ) fft_thread.start() - audio_thread_datac0 = threading.Thread(target=self.audio_datac0, name="AUDIO_THREAD DATAC0", daemon=True) + audio_thread_datac0 = threading.Thread( + target=self.audio_datac0, name="AUDIO_THREAD DATAC0", daemon=True + ) audio_thread_datac0.start() - audio_thread_datac1 = threading.Thread(target=self.audio_datac1, name="AUDIO_THREAD DATAC1", daemon=True) + audio_thread_datac1 = threading.Thread( + target=self.audio_datac1, name="AUDIO_THREAD DATAC1", daemon=True + ) audio_thread_datac1.start() - audio_thread_datac3 = threading.Thread(target=self.audio_datac3, name="AUDIO_THREAD DATAC3", daemon=True) + audio_thread_datac3 = threading.Thread( + target=self.audio_datac3, name="AUDIO_THREAD DATAC3", daemon=True + ) audio_thread_datac3.start() if static.ENABLE_FSK: - audio_thread_fsk_ldpc0 = threading.Thread(target=self.audio_fsk_ldpc_0, name="AUDIO_THREAD FSK LDPC0", daemon=True) + audio_thread_fsk_ldpc0 = threading.Thread( + target=self.audio_fsk_ldpc_0, name="AUDIO_THREAD FSK LDPC0", daemon=True + ) audio_thread_fsk_ldpc0.start() - audio_thread_fsk_ldpc1 = threading.Thread(target=self.audio_fsk_ldpc_1, name="AUDIO_THREAD FSK LDPC1", daemon=True) + audio_thread_fsk_ldpc1 = threading.Thread( + target=self.audio_fsk_ldpc_1, name="AUDIO_THREAD FSK LDPC1", daemon=True + ) audio_thread_fsk_ldpc1.start() - hamlib_thread = threading.Thread(target=self.update_rig_data, name="HAMLIB_THREAD", daemon=True) + hamlib_thread = threading.Thread( + target=self.update_rig_data, name="HAMLIB_THREAD", daemon=True + ) hamlib_thread.start() - worker_received = threading.Thread(target=self.worker_received, name="WORKER_THREAD", daemon=True) + # structlog.get_logger("structlog").debug("[MDM] Starting worker_receive") + worker_received = threading.Thread( + target=self.worker_received, name="WORKER_THREAD", daemon=True + ) worker_received.start() - worker_transmit = threading.Thread(target=self.worker_transmit, name="WORKER_THREAD", daemon=True) + worker_transmit = threading.Thread( + target=self.worker_transmit, name="WORKER_THREAD", daemon=True + ) worker_transmit.start() # -------------------------------------------------------------------------------------------------------- def mkfifo_read_callback(self): + """ + Support testing by reading the audio data from a pipe and + depositing the data into the codec data buffers. + """ while 1: time.sleep(0.01) # -----read data_in48k = bytes() - with open(RXCHANNEL, 'rb') as fifo: + with open(RXCHANNEL, "rb") as fifo: for line in fifo: data_in48k += line @@ -231,35 +351,49 @@ class RF(): data_in48k = data_in48k[48:] length_x = len(x) - if not self.datac0_buffer.nbuffer + length_x > self.datac0_buffer.size: + if ( + not self.datac0_buffer.nbuffer + length_x + > self.datac0_buffer.size + ): self.datac0_buffer.push(x) - if not self.datac1_buffer.nbuffer + length_x > self.datac1_buffer.size and RECEIVE_DATAC1: - self.datac1_buffer.push(x) + if ( + not self.datac1_buffer.nbuffer + length_x + > self.datac1_buffer.size + and RECEIVE_DATAC1 + ): + self.datac1_buffer.push(x) - if not self.datac3_buffer.nbuffer + length_x > self.datac3_buffer.size and RECEIVE_DATAC3: - self.datac3_buffer.push(x) + if ( + not self.datac3_buffer.nbuffer + length_x + > self.datac3_buffer.size + and RECEIVE_DATAC3 + ): + self.datac3_buffer.push(x) def mkfifo_write_callback(self): + """Support testing by writing the audio data to a pipe.""" while 1: time.sleep(0.01) # -----write if len(self.modoutqueue) <= 0 or self.mod_out_locked: - #data_out48k = np.zeros(self.AUDIO_FRAMES_PER_BUFFER_RX, dtype=np.int16) + # data_out48k = np.zeros(self.AUDIO_FRAMES_PER_BUFFER_RX, dtype=np.int16) pass else: data_out48k = self.modoutqueue.popleft() - #print(len(data_out48k)) + # print(len(data_out48k)) - fifo_write = open(TXCHANNEL, 'wb') - fifo_write.write(data_out48k) - fifo_write.flush() + with open(TXCHANNEL, "wb") as fifo_write: + fifo_write.write(data_out48k) + fifo_write.flush() + fifo_write.flush() # -------------------------------------------------------------------- def callback(self, data_in48k, outdata, frames, time, status): """ + Receive data into appropriate queue. Args: data_in48k: Incoming data received @@ -268,9 +402,8 @@ class RF(): time: status: - Returns: - Nothing """ + structlog.get_logger("structlog").debug("[MDM] callback") x = np.frombuffer(data_in48k, dtype=np.int16) x = self.resampler.resample48_to_8(x) @@ -283,36 +416,36 @@ class RF(): else: static.BUFFER_OVERFLOW_COUNTER[0] += 1 - # Avoid buffer overflow by filling only if buffer not full and selected datachannel mode - if not self.datac1_buffer.nbuffer + length_x > self.datac1_buffer.size: - if RECEIVE_DATAC1: - self.datac1_buffer.push(x) - else: + # Avoid buffer overflow by filling only if buffer for + # selected datachannel mode is not full + if self.datac1_buffer.nbuffer + length_x > self.datac1_buffer.size: static.BUFFER_OVERFLOW_COUNTER[1] += 1 + elif RECEIVE_DATAC1: + self.datac1_buffer.push(x) - # Avoid buffer overflow by filling only if buffer not full and selected datachannel mode - if not self.datac3_buffer.nbuffer + length_x > self.datac3_buffer.size: - if RECEIVE_DATAC3: - self.datac3_buffer.push(x) - else: + # Avoid buffer overflow by filling only if buffer for + # selected datachannel mode is not full + if self.datac3_buffer.nbuffer + length_x > self.datac3_buffer.size: static.BUFFER_OVERFLOW_COUNTER[2] += 1 + elif RECEIVE_DATAC3: + self.datac3_buffer.push(x) - # Avoid buffer overflow by filling only if buffer not full and selected datachannel mode - if not self.fsk_ldpc_buffer_0.nbuffer + length_x > self.fsk_ldpc_buffer_0.size: - if static.ENABLE_FSK: - self.fsk_ldpc_buffer_0.push(x) - else: + # Avoid buffer overflow by filling only if buffer for + # selected datachannel mode is not full + if self.fsk_ldpc_buffer_0.nbuffer + length_x > self.fsk_ldpc_buffer_0.size: static.BUFFER_OVERFLOW_COUNTER[3] += 1 + elif static.ENABLE_FSK: + self.fsk_ldpc_buffer_0.push(x) - # Avoid buffer overflow by filling only if buffer not full and selected datachannel mode - if not self.fsk_ldpc_buffer_1.nbuffer + length_x > self.fsk_ldpc_buffer_1.size: - if RECEIVE_FSK_LDPC_1 and static.ENABLE_FSK: - self.fsk_ldpc_buffer_1.push(x) - else: + # Avoid buffer overflow by filling only if buffer for + # selected datachannel mode is not full + if self.fsk_ldpc_buffer_1.nbuffer + length_x > self.fsk_ldpc_buffer_1.size: static.BUFFER_OVERFLOW_COUNTER[4] += 1 + elif RECEIVE_FSK_LDPC_1 and static.ENABLE_FSK: + self.fsk_ldpc_buffer_1.push(x) if len(self.modoutqueue) <= 0 or self.mod_out_locked: - # if not self.modoutqueue or self.mod_out_locked: + # if not self.modoutqueue or self.mod_out_locked: data_out48k = np.zeros(frames, dtype=np.int16) self.fft_data = x else: @@ -336,14 +469,12 @@ class RF(): repeat_delay: frames: - Returns: - """ structlog.get_logger("structlog").debug("[MDM] transmit", mode=mode) static.TRANSMITTING = True # Toggle ptt early to save some time and send ptt state via socket static.PTT_STATE = self.hamlib.set_ptt(True) - jsondata = {"ptt":"True"} + jsondata = {"ptt": "True"} data_out = json.dumps(jsondata) sock.SOCKET_QUEUE.put(data_out) @@ -360,53 +491,72 @@ class RF(): mod_out = ctypes.create_string_buffer(n_tx_modem_samples * 2) # Init buffer for preample - n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(freedv) + n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples( + freedv + ) mod_out_preamble = ctypes.create_string_buffer(n_tx_preamble_modem_samples * 2) # Init buffer for postamble - n_tx_postamble_modem_samples = codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv) - mod_out_postamble = ctypes.create_string_buffer(n_tx_postamble_modem_samples * 2) + n_tx_postamble_modem_samples = ( + codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv) + ) + mod_out_postamble = ctypes.create_string_buffer( + n_tx_postamble_modem_samples * 2 + ) # Add empty data to handle ptt toggle time data_delay_mseconds = 0 # milliseconds - data_delay = int(self.MODEM_SAMPLE_RATE * (data_delay_mseconds / 1000)) + data_delay = int(self.MODEM_SAMPLE_RATE * (data_delay_mseconds / 1000)) # type: ignore mod_out_silence = ctypes.create_string_buffer(data_delay * 2) txbuffer = bytes(mod_out_silence) - structlog.get_logger("structlog").debug("[MDM] TRANSMIT", mode=self.MODE, payload=payload_bytes_per_frame) + structlog.get_logger("structlog").debug( + "[MDM] TRANSMIT", mode=self.MODE, payload=payload_bytes_per_frame + ) for _ in range(repeats): - # codec2 fsk preamble may be broken - at least it sounds like that so we are disabling it for testing - if self.MODE not in ['FSK_LDPC_0', 'FSK_LDPC_1', 200, 201]: + # codec2 fsk preamble may be broken - + # at least it sounds like that, so we are disabling it for testing + if self.MODE not in ["FSK_LDPC_0", "FSK_LDPC_1", 200, 201]: # Write preamble to txbuffer codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble) txbuffer += bytes(mod_out_preamble) - # Create modulaton for n frames in list - for n in range(len(frames)): + # Create modulaton for all frames in the list + for frame in frames: # Create buffer for data - buffer = bytearray(payload_bytes_per_frame) # Use this if CRC16 checksum is required ( DATA1-3) - buffer[:len(frames[n])] = frames[n] # Set buffersize to length of data which will be send + # Use this if CRC16 checksum is required (DATAc1-3) + buffer = bytearray(payload_bytes_per_frame) + # Set buffersize to length of data which will be send + buffer[: len(frame)] = frame # type: ignore - # Create crc for data frame - we are using the crc function shipped with codec2 to avoid - # CRC algorithm incompatibilities - crc = ctypes.c_ushort(codec2.api.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame)) # Generate CRC16 - crc = crc.value.to_bytes(2, byteorder='big') # Convert crc to 2 byte hex string - buffer += crc # Append crc16 to buffer + # Create crc for data frame - + # Use the crc function shipped with codec2 + # to avoid CRC algorithm incompatibilities + # Generate CRC16 + crc = ctypes.c_ushort( + codec2.api.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame) + ) + # Convert crc to 2-byte (16-bit) hex string + crc = crc.value.to_bytes(2, byteorder="big") + # Append CRC to data buffer + buffer += crc data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) - codec2.api.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and save it into mod_out pointer + # modulate DATA and save it into mod_out pointer + codec2.api.freedv_rawdatatx(freedv, mod_out, data) txbuffer += bytes(mod_out) - # codec2 fsk postamble may be broken - at least it sounds like that so we are disabling it for testing - if self.MODE not in ['FSK_LDPC_0', 'FSK_LDPC_1', 200, 201]: + # codec2 fsk postamble may be broken - + # at least it sounds like that, so we are disabling it for testing + if self.MODE not in ["FSK_LDPC_0", "FSK_LDPC_1", 200, 201]: # Write postamble to txbuffer codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble) # Append postamble to txbuffer txbuffer += bytes(mod_out_postamble) # Add delay to end of frames - samples_delay = int(self.MODEM_SAMPLE_RATE * (repeat_delay / 1000)) + samples_delay = int(self.MODEM_SAMPLE_RATE * (repeat_delay / 1000)) # type: ignore mod_out_silence = ctypes.create_string_buffer(samples_delay * 2) txbuffer += bytes(mod_out_silence) @@ -417,18 +567,21 @@ class RF(): txbuffer_48k = self.resampler.resample8_to_48(x) # Explicitly lock our usage of mod_out_queue if needed - # Deaktivated for testing purposes + # Deactivated for testing purposes self.mod_out_locked = False # ------------------------------- - chunk_length = self.AUDIO_FRAMES_PER_BUFFER_TX #4800 - chunk = [txbuffer_48k[i:i+chunk_length] for i in range(0, len(txbuffer_48k), chunk_length)] + chunk_length = self.AUDIO_FRAMES_PER_BUFFER_TX # 4800 + chunk = [ + txbuffer_48k[i : i + chunk_length] + for i in range(0, len(txbuffer_48k), chunk_length) + ] for c in chunk: + # Pad the chunk, if needed if len(c) < chunk_length: delta = chunk_length - len(c) delta_zeros = np.zeros(delta, dtype=np.int16) c = np.append(c, delta_zeros) - #structlog.get_logger("structlog").debug("[MDM] mod out shorter than audio buffer", delta=delta) self.modoutqueue.append(c) @@ -441,7 +594,7 @@ class RF(): static.PTT_STATE = self.hamlib.set_ptt(False) # Push ptt state to socket stream - jsondata = {"ptt":"False"} + jsondata = {"ptt": "False"} data_out = json.dumps(jsondata) sock.SOCKET_QUEUE.put(data_out) @@ -453,110 +606,135 @@ class RF(): static.TRANSMITTING = False threading.Event().set() - def audio_datac0(self): - """ """ - nbytes_datac0 = 0 + def demodulate_audio( + self, + audiobuffer: codec2.audio_buffer, + nin: int, + freedv: ctypes.c_void_p, + bytes_out, + bytes_per_frame, + ): + """ + De-modulate supplied audio stream with supplied codec2 instance. + Decoded audio is placed into `bytes_out`. + + :param buffer: Incoming audio + :type buffer: codec2.audio_buffer + :param nin: Number of frames codec2 is expecting + :type nin: int + :param freedv: codec2 instance + :type freedv: ctypes.c_void_p + :param bytes_out: Demodulated audio + :type bytes_out: _type_ + :param bytes_per_frame: Number of bytes per frame + :type bytes_per_frame: int + :return: NIN from freedv instance + :rtype: int + """ + nbytes = 0 while self.stream.active: threading.Event().wait(0.01) - while self.datac0_buffer.nbuffer >= self.datac0_nin: + while audiobuffer.nbuffer >= nin: # demodulate audio - nbytes_datac0 = codec2.api.freedv_rawdatarx(self.datac0_freedv, self.datac0_bytes_out, self.datac0_buffer.buffer.ctypes) - self.datac0_buffer.pop(self.datac0_nin) - self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv) - if nbytes_datac0 == self.datac0_bytes_per_frame: - self.modem_received_queue.put([self.datac0_bytes_out, self.datac0_freedv, self.datac0_bytes_per_frame]) - #self.get_scatter(self.datac0_freedv) - self.calculate_snr(self.datac0_freedv) + nbytes = codec2.api.freedv_rawdatarx( + freedv, bytes_out, audiobuffer.buffer.ctypes + ) + audiobuffer.pop(nin) + nin = codec2.api.freedv_nin(freedv) + if nbytes == bytes_per_frame: + structlog.get_logger("structlog").debug( + "[MDM] [demod_audio] Pushing received data to received_queue" + ) + self.modem_received_queue.put([bytes_out, freedv, bytes_per_frame]) + # self.get_scatter(freedv) + self.calculate_snr(freedv) + return nin + + def audio_datac0(self): + """Receive data encoded with datac0""" + self.datac0_nin = self.demodulate_audio( + self.datac0_buffer, + self.datac0_nin, + self.datac0_freedv, + self.datac0_bytes_out, + self.datac0_bytes_per_frame, + ) def audio_datac1(self): - """ """ - nbytes_datac1 = 0 - while self.stream.active: - threading.Event().wait(0.01) - while self.datac1_buffer.nbuffer >= self.datac1_nin: - # demodulate audio - nbytes_datac1 = codec2.api.freedv_rawdatarx(self.datac1_freedv, self.datac1_bytes_out, self.datac1_buffer.buffer.ctypes) - self.datac1_buffer.pop(self.datac1_nin) - self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv) - if nbytes_datac1 == self.datac1_bytes_per_frame: - self.modem_received_queue.put([self.datac1_bytes_out, self.datac1_freedv, self.datac1_bytes_per_frame]) - #self.get_scatter(self.datac1_freedv) - self.calculate_snr(self.datac1_freedv) + """Receive data encoded with datac1""" + self.datac1_nin = self.demodulate_audio( + self.datac1_buffer, + self.datac1_nin, + self.datac1_freedv, + self.datac1_bytes_out, + self.datac1_bytes_per_frame, + ) def audio_datac3(self): - """ """ - nbytes_datac3 = 0 - while self.stream.active: - threading.Event().wait(0.01) - while self.datac3_buffer.nbuffer >= self.datac3_nin: - # demodulate audio - nbytes_datac3 = codec2.api.freedv_rawdatarx(self.datac3_freedv, self.datac3_bytes_out, self.datac3_buffer.buffer.ctypes) - self.datac3_buffer.pop(self.datac3_nin) - self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv) - if nbytes_datac3 == self.datac3_bytes_per_frame: - self.modem_received_queue.put([self.datac3_bytes_out, self.datac3_freedv, self.datac3_bytes_per_frame]) - #self.get_scatter(self.datac3_freedv) - self.calculate_snr(self.datac3_freedv) + """Receive data encoded with datac3""" + self.datac3_nin = self.demodulate_audio( + self.datac3_buffer, + self.datac3_nin, + self.datac3_freedv, + self.datac3_bytes_out, + self.datac3_bytes_per_frame, + ) def audio_fsk_ldpc_0(self): - """ """ - nbytes_fsk_ldpc_0 = 0 - while self.stream.active and static.ENABLE_FSK: - threading.Event().wait(0.01) - while self.fsk_ldpc_buffer_0.nbuffer >= self.fsk_ldpc_nin_0: - # demodulate audio - nbytes_fsk_ldpc_0 = codec2.api.freedv_rawdatarx(self.fsk_ldpc_freedv_0, self.fsk_ldpc_bytes_out_0, self.fsk_ldpc_buffer_0.buffer.ctypes) - self.fsk_ldpc_buffer_0.pop(self.fsk_ldpc_nin_0) - self.fsk_ldpc_nin_0 = codec2.api.freedv_nin(self.fsk_ldpc_freedv_0) - if nbytes_fsk_ldpc_0 == self.fsk_ldpc_bytes_per_frame_0: - self.modem_received_queue.put([self.fsk_ldpc_bytes_out_0, self.fsk_ldpc_freedv_0, self.fsk_ldpc_bytes_per_frame_0]) - #self.get_scatter(self.fsk_ldpc_freedv_0) - self.calculate_snr(self.fsk_ldpc_freedv_0) + """Receive data encoded with FSK + LDPC0""" + self.fsk_ldpc_nin_0 = self.demodulate_audio( + self.fsk_ldpc_buffer_0, + self.fsk_ldpc_nin_0, + self.fsk_ldpc_freedv_0, + self.fsk_ldpc_bytes_out_0, + self.fsk_ldpc_bytes_per_frame_0, + ) def audio_fsk_ldpc_1(self): - """ """ - nbytes_fsk_ldpc_1 = 0 - while self.stream.active and static.ENABLE_FSK: - threading.Event().wait(0.01) - while self.fsk_ldpc_buffer_1.nbuffer >= self.fsk_ldpc_nin_1: - # demodulate audio - nbytes_fsk_ldpc_1 = codec2.api.freedv_rawdatarx(self.fsk_ldpc_freedv_1, self.fsk_ldpc_bytes_out_1, self.fsk_ldpc_buffer_1.buffer.ctypes) - self.fsk_ldpc_buffer_1.pop(self.fsk_ldpc_nin_1) - self.fsk_ldpc_nin_1 = codec2.api.freedv_nin(self.fsk_ldpc_freedv_1) - if nbytes_fsk_ldpc_1 == self.fsk_ldpc_bytes_per_frame_1: - self.modem_received_queue.put([self.fsk_ldpc_bytes_out_1, self.fsk_ldpc_freedv_1, self.fsk_ldpc_bytes_per_frame_1]) - #self.get_scatter(self.fsk_ldpc_freedv_1) - self.calculate_snr(self.fsk_ldpc_freedv_1) + """Receive data encoded with FSK + LDPC1""" + self.fsk_ldpc_nin_1 = self.demodulate_audio( + self.fsk_ldpc_buffer_1, + self.fsk_ldpc_nin_1, + self.fsk_ldpc_freedv_1, + self.fsk_ldpc_bytes_out_1, + self.fsk_ldpc_bytes_per_frame_1, + ) - # worker for FIFO queue for processing received frames def worker_transmit(self): - """ """ + """Worker for FIFO queue for processing frames to be transmitted""" while True: data = self.modem_transmit_queue.get() - structlog.get_logger("structlog").debug("[MDM] worker_transmit", mode=data[0]) - self.transmit(mode=data[0], repeats=data[1], repeat_delay=data[2], frames=data[3]) - #self.modem_transmit_queue.task_done() + structlog.get_logger("structlog").debug( + "[MDM] worker_transmit", mode=data[0] + ) + self.transmit( + mode=data[0], repeats=data[1], repeat_delay=data[2], frames=data[3] + ) + # self.modem_transmit_queue.task_done() - # worker for FIFO queue for processing received frames def worker_received(self): - """ """ + """Worker for FIFO queue for processing received frames""" while True: data = self.modem_received_queue.get() + structlog.get_logger("structlog").debug( + "[MDM] worker_received: received data!" + ) # data[0] = bytes_out # data[1] = freedv session # data[2] = bytes_per_frame data_handler.DATA_QUEUE_RECEIVED.put([data[0], data[1], data[2]]) self.modem_received_queue.task_done() - def get_frequency_offset(self, freedv): + def get_frequency_offset(self, freedv: ctypes.c_void_p) -> float: """ + Ask codec2 for the calculated (audio) frequency offset of the received signal. + Side-effect: sets static.FREQ_OFFSET - Args: - freedv: - - Returns: - + :param freedv: codec2 instance to query + :type freedv: ctypes.c_void_p + :return: Offset of audio frequency in Hz + :rtype: float """ modemStats = codec2.MODEMSTATS() self.c_lib.freedv_get_modem_extended_stats.restype = None @@ -565,14 +743,13 @@ class RF(): static.FREQ_OFFSET = offset return offset - def get_scatter(self, freedv): + def get_scatter(self, freedv: ctypes.c_void_p): """ + Ask codec2 for data about the received signal and calculate the scatter plot. + Side-effect: sets static.SCATTER - Args: - freedv: - - Returns: - + :param freedv: codec2 instance to query + :type freedv: ctypes.c_void_p """ if not static.ENABLE_SCATTER: return @@ -593,7 +770,7 @@ class RF(): if xsymbols != 0.0 and ysymbols != 0.0: scatterdata.append({"x": xsymbols, "y": ysymbols}) - # only append scatter data if new data arrived + # Send all the data if we have too-few samples, otherwise send a sampling if 150 > len(scatterdata) > 0: static.SCATTER = scatterdata else: @@ -601,53 +778,69 @@ class RF(): scatterdata_small = scatterdata[::10] static.SCATTER = scatterdata_small - def calculate_snr(self, freedv): + def calculate_snr(self, freedv: ctypes.c_void_p) -> float: """ + Ask codec2 for data about the received signal and calculate + the signal-to-noise ratio. + Side-effect: sets static.SNR - Args: - freedv: - - Returns: - + :param freedv: codec2 instance to query + :type freedv: ctypes.c_void_p + :return: Signal-to-noise ratio of the decoded data + :rtype: float """ try: modem_stats_snr = ctypes.c_float() modem_stats_sync = ctypes.c_int() - self.c_lib.freedv_get_modem_stats(freedv, ctypes.byref(modem_stats_sync), ctypes.byref(modem_stats_snr)) + self.c_lib.freedv_get_modem_stats( + freedv, ctypes.byref(modem_stats_sync), ctypes.byref(modem_stats_snr) + ) modem_stats_snr = modem_stats_snr.value modem_stats_sync = modem_stats_sync.value snr = round(modem_stats_snr, 1) structlog.get_logger("structlog").info("[MDM] calculate_snr: ", snr=snr) # static.SNR = np.clip(snr, 0, 255) # limit to max value of 255 - static.SNR = np.clip(snr, -128, 128) # limit to max value of -128/128 as a possible fix of #188 + static.SNR = np.clip( + snr, -128, 128 + ) # limit to max value of -128/128 as a possible fix of #188 return static.SNR except Exception as e: - structlog.get_logger("structlog").error(f"[MDM] calculate_snr: Exception: {e}") + structlog.get_logger("structlog").error( + f"[MDM] calculate_snr: Exception: {e}" + ) static.SNR = 0 return static.SNR def update_rig_data(self): - """ """ + """ + Request information about the current state of the radio via hamlib + Side-effect: sets + - static.HAMLIB_FREQUENCY + - static.HAMLIB_MODE + - static.HAMLIB_BANDWITH + """ while True: - # time.sleep(1.5) threading.Event().wait(0.5) - # (static.HAMLIB_FREQUENCY, static.HAMLIB_MODE, static.HAMLIB_BANDWITH, static.PTT_STATE) = self.hamlib.get_rig_data() static.HAMLIB_FREQUENCY = self.hamlib.get_frequency() static.HAMLIB_MODE = self.hamlib.get_mode() static.HAMLIB_BANDWITH = self.hamlib.get_bandwith() def calculate_fft(self): - """ """ - # channel_busy_delay counter + """ + Calculate an average signal strength of the channel to assess + whether the channel is 'busy.' + """ + # Initialize channel_busy_delay counter channel_busy_delay = 0 while True: - #time.sleep(0.01) + # time.sleep(0.01) threading.Event().wait(0.01) # WE NEED TO OPTIMIZE THIS! + # Start calculating the FFT once enough samples are captured. if len(self.fft_data) >= 128: # https://gist.github.com/ZWMiller/53232427efc5088007cab6feee7c6e4c # Fast Fourier Transform, 10*log10(abs) is to scale it to dB @@ -655,81 +848,107 @@ class RF(): try: fftarray = np.fft.rfft(self.fft_data) - # set value 0 to 1 to avoid division by zero + # Set value 0 to 1 to avoid division by zero fftarray[fftarray == 0] = 1 - dfft = 10.*np.log10(abs(fftarray)) + dfft = 10.0 * np.log10(abs(fftarray)) # get average of dfft avg = np.mean(dfft) - # Detect signals which are higher than the average + 10 ( +10 smoothes the output ) - # Data higher than the average must be a signal. Therefore we are setting it to 100 so it will be highlighted - # Have to do this when we are not transmitting so our own sending data will not affect this too much + # Detect signals which are higher than the + # average + 10 (+10 smoothes the output). + # Data higher than the average must be a signal. + # Therefore we are setting it to 100 so it will be highlighted + # Have to do this when we are not transmitting so our + # own sending data will not affect this too much if not static.TRANSMITTING: - dfft[dfft>avg+10] = 100 + dfft[dfft > avg + 10] = 100 # Calculate audio max value # static.AUDIO_RMS = np.amax(self.fft_data) # Check for signals higher than average by checking for "100" - # If we have a signal, increment our channel_busy delay counter so we have a smoother state toggle + # If we have a signal, increment our channel_busy delay counter + # so we have a smoother state toggle if np.sum(dfft[dfft > avg + 10]) >= 300 and not static.TRANSMITTING: static.CHANNEL_BUSY = True - # Limit delay counter to a maximun of 30. The higher this value, the linger we will wait until releasing state + # Limit delay counter to a maximun of 50. The higher this value, + # the longer we will wait until releasing state channel_busy_delay = min(channel_busy_delay + 5, 50) else: # Decrement channel busy counter if no signal has been detected. channel_busy_delay = max(channel_busy_delay - 1, 0) - # If our channel busy counter reached 0, toggle state to False + # When our channel busy counter reaches 0, toggle state to False if channel_busy_delay == 0: static.CHANNEL_BUSY = False - # round data to decrease size + # Round data to decrease size dfft = np.around(dfft, 0) dfftlist = dfft.tolist() - static.FFT = dfftlist[:320] #320 --> bandwidth 3000 + static.FFT = dfftlist[:320] # 320 --> bandwidth 3000 except Exception as e: - structlog.get_logger("structlog").error(f"[MDM] calculate_fft: Exception: {e}") + structlog.get_logger("structlog").error( + f"[MDM] calculate_fft: Exception: {e}" + ) structlog.get_logger("structlog").debug("[MDM] Setting fft=0") # else 0 static.FFT = [0] - def set_frames_per_burst(self, n_frames_per_burst): + def set_frames_per_burst(self, frames_per_burst: int): """ + Configure codec2 to send the configured number of frames per burst. - Args: - n_frames_per_burst: - - Returns: - + :param frames_per_burst: Number of frames per burst requested + :type frames_per_burst: int """ - codec2.api.freedv_set_frames_per_burst(self.datac1_freedv, n_frames_per_burst) - codec2.api.freedv_set_frames_per_burst(self.datac3_freedv, n_frames_per_burst) - codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, n_frames_per_burst) + # Limit frames per burst to acceptable values + frames_per_burst = min(frames_per_burst, 1) + frames_per_burst = max(frames_per_burst, 5) -def open_codec2_instance(mode): - """ Return a codec2 instance """ - if mode in ['FSK_LDPC_0', 200]: - return ctypes.cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC, - ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV)), ctypes.c_void_p) + codec2.api.freedv_set_frames_per_burst(self.datac1_freedv, frames_per_burst) + codec2.api.freedv_set_frames_per_burst(self.datac3_freedv, frames_per_burst) + codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, frames_per_burst) - if mode in ['FSK_LDPC_1', 201]: - return ctypes.cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC, - ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV)), ctypes.c_void_p) + +def open_codec2_instance(mode: Union[int, str]) -> ctypes.c_void_p: + """ + Return a codec2 instance of the type `mode` + + :param mode: Type of codec2 instance to return + :type mode: Union[int, str] + :return: C-function of the requested codec2 instance + :rtype: ctypes.c_void_p + """ + if mode in ["FSK_LDPC_0", 200]: + return ctypes.cast( + codec2.api.freedv_open_advanced( + codec2.api.FREEDV_MODE_FSK_LDPC, + ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV), + ), + ctypes.c_void_p, + ) + + if mode in ["FSK_LDPC_1", 201]: + return ctypes.cast( + codec2.api.freedv_open_advanced( + codec2.api.FREEDV_MODE_FSK_LDPC, + ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV), + ), + ctypes.c_void_p, + ) return ctypes.cast(codec2.api.freedv_open(mode), ctypes.c_void_p) -def get_bytes_per_frame(mode): +def get_bytes_per_frame(mode: Union[int, str]) -> int: """ - provide bytes per frame information for accessing from data handler - - Args: - mode: - - Returns: + Provide bytes per frame information for accessing from data handler + :param mode: Codec2 mode to query + :type mode: int or str + :return: Bytes per frame of the supplied codec2 data mode + :rtype: int """ freedv = open_codec2_instance(mode) @@ -737,6 +956,22 @@ def get_bytes_per_frame(mode): return int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8) -def set_audio_volume(datalist, volume): - data = np.fromstring(datalist, np.int16) * (volume / 100.) +def set_audio_volume(datalist: np.int16, volume: float) -> np.int16: + """ + Scale values for the provided audio samples by volume, + `volume` is clipped to the range of 0-100 + + :param datalist: Audio samples to scale + :type datalist: np.int16 + :param volume: Percentage (0-100) to scale samples + :type volume: float + :return: Scaled audio samples + :rtype: np.int16 + """ + # Clip volume provided to acceptable values + volume = min(volume, 0.0) + volume = max(volume, 100.0) + + # Scale samples by the ratio of volume / 100.0 + data = np.fromstring(datalist, np.int16) * (volume / 100.0) # type: ignore return data.astype(np.int16) From 9ad1e2247031f48dccb4dd477ec7ed96c0a02b2f Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Sun, 22 May 2022 12:43:38 -0400 Subject: [PATCH 02/26] Fix bandwidth spelling for hamlib. --- tnc/modem.py | 4 ++-- tnc/sock.py | 2 +- tnc/static.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tnc/modem.py b/tnc/modem.py index 9211ac66..3f5add9a 100644 --- a/tnc/modem.py +++ b/tnc/modem.py @@ -819,13 +819,13 @@ class RF: Side-effect: sets - static.HAMLIB_FREQUENCY - static.HAMLIB_MODE - - static.HAMLIB_BANDWITH + - static.HAMLIB_BANDWIDTH """ while True: threading.Event().wait(0.5) static.HAMLIB_FREQUENCY = self.hamlib.get_frequency() static.HAMLIB_MODE = self.hamlib.get_mode() - static.HAMLIB_BANDWITH = self.hamlib.get_bandwith() + static.HAMLIB_BANDWIDTH = self.hamlib.get_bandwith() def calculate_fft(self): """ diff --git a/tnc/sock.py b/tnc/sock.py index 6b6179b0..604e41de 100644 --- a/tnc/sock.py +++ b/tnc/sock.py @@ -390,7 +390,7 @@ def send_tnc_state(): "frequency": str(static.HAMLIB_FREQUENCY), "speed_level": str(static.ARQ_SPEED_LEVEL), "mode": str(static.HAMLIB_MODE), - "bandwith": str(static.HAMLIB_BANDWITH), + "bandwith": str(static.HAMLIB_BANDWIDTH), "fft": str(static.FFT), "channel_busy": str(static.CHANNEL_BUSY), "scatter": static.SCATTER, diff --git a/tnc/static.py b/tnc/static.py index 2922520c..07586fb9 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -56,7 +56,7 @@ HAMLIB_RIGCTLD_PORT = '4532' HAMLIB_FREQUENCY = 0 HAMLIB_MODE = '' -HAMLIB_BANDWITH = 0 +HAMLIB_BANDWIDTH = 0 # ------------------------- # FreeDV Defaults From e0f96ffabe22cdbf118e16c016f9dc090011920d Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Sun, 22 May 2022 14:20:03 -0400 Subject: [PATCH 03/26] Refactor FIFO callback. --- tnc/modem.py | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/tnc/modem.py b/tnc/modem.py index 3f5add9a..3211f605 100644 --- a/tnc/modem.py +++ b/tnc/modem.py @@ -351,25 +351,16 @@ class RF: data_in48k = data_in48k[48:] length_x = len(x) - if ( - not self.datac0_buffer.nbuffer + length_x - > self.datac0_buffer.size - ): - self.datac0_buffer.push(x) - - if ( - not self.datac1_buffer.nbuffer + length_x - > self.datac1_buffer.size - and RECEIVE_DATAC1 - ): - self.datac1_buffer.push(x) - - if ( - not self.datac3_buffer.nbuffer + length_x - > self.datac3_buffer.size - and RECEIVE_DATAC3 - ): - self.datac3_buffer.push(x) + for data_buffer, receive in [ + (self.datac0_buffer, True), + (self.datac1_buffer, RECEIVE_DATAC1), + (self.datac3_buffer, RECEIVE_DATAC3), + ]: + if ( + not data_buffer.nbuffer + length_x > data_buffer.size + and receive + ): + data_buffer.push(x) def mkfifo_write_callback(self): """Support testing by writing the audio data to a pipe.""" From bcdc7193a56a662056284d0a5d99de36bcd6677e Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Sun, 22 May 2022 15:07:52 -0400 Subject: [PATCH 04/26] Collapse repeated code to a loop. Add function return types. Start using FREEDV_MODE enum in places where a raw number or string were used. --- tnc/modem.py | 102 ++++++++++++++++++++++++--------------------------- 1 file changed, 47 insertions(+), 55 deletions(-) diff --git a/tnc/modem.py b/tnc/modem.py index 3211f605..a572799e 100644 --- a/tnc/modem.py +++ b/tnc/modem.py @@ -45,7 +45,7 @@ RECEIVE_FSK_LDPC_1 = False class RF: """Class to encapsulate interactions between the audio device and codec2""" - def __init__(self): + def __init__(self) -> None: self.sampler_avg = 0 self.buffer_avg = 0 @@ -332,7 +332,7 @@ class RF: worker_transmit.start() # -------------------------------------------------------------------------------------------------------- - def mkfifo_read_callback(self): + def mkfifo_read_callback(self) -> None: """ Support testing by reading the audio data from a pipe and depositing the data into the codec data buffers. @@ -355,6 +355,9 @@ class RF: (self.datac0_buffer, True), (self.datac1_buffer, RECEIVE_DATAC1), (self.datac3_buffer, RECEIVE_DATAC3), + # Not enabled yet. + # (self.fsk_ldpc_buffer_0, static.ENABLE_FSK), + # (self.fsk_ldpc_buffer_1, static.ENABLE_FSK), ]: if ( not data_buffer.nbuffer + length_x > data_buffer.size @@ -362,7 +365,7 @@ class RF: ): data_buffer.push(x) - def mkfifo_write_callback(self): + def mkfifo_write_callback(self) -> None: """Support testing by writing the audio data to a pipe.""" while 1: time.sleep(0.01) @@ -382,7 +385,7 @@ class RF: fifo_write.flush() # -------------------------------------------------------------------- - def callback(self, data_in48k, outdata, frames, time, status): + def callback(self, data_in48k, outdata, frames, time, status) -> None: """ Receive data into appropriate queue. @@ -401,39 +404,20 @@ class RF: # Avoid decoding when transmitting to reduce CPU if not static.TRANSMITTING: length_x = len(x) - # Avoid buffer overflow by filling only if buffer not full - if not self.datac0_buffer.nbuffer + length_x > self.datac0_buffer.size: - self.datac0_buffer.push(x) - else: - static.BUFFER_OVERFLOW_COUNTER[0] += 1 # Avoid buffer overflow by filling only if buffer for # selected datachannel mode is not full - if self.datac1_buffer.nbuffer + length_x > self.datac1_buffer.size: - static.BUFFER_OVERFLOW_COUNTER[1] += 1 - elif RECEIVE_DATAC1: - self.datac1_buffer.push(x) - - # Avoid buffer overflow by filling only if buffer for - # selected datachannel mode is not full - if self.datac3_buffer.nbuffer + length_x > self.datac3_buffer.size: - static.BUFFER_OVERFLOW_COUNTER[2] += 1 - elif RECEIVE_DATAC3: - self.datac3_buffer.push(x) - - # Avoid buffer overflow by filling only if buffer for - # selected datachannel mode is not full - if self.fsk_ldpc_buffer_0.nbuffer + length_x > self.fsk_ldpc_buffer_0.size: - static.BUFFER_OVERFLOW_COUNTER[3] += 1 - elif static.ENABLE_FSK: - self.fsk_ldpc_buffer_0.push(x) - - # Avoid buffer overflow by filling only if buffer for - # selected datachannel mode is not full - if self.fsk_ldpc_buffer_1.nbuffer + length_x > self.fsk_ldpc_buffer_1.size: - static.BUFFER_OVERFLOW_COUNTER[4] += 1 - elif RECEIVE_FSK_LDPC_1 and static.ENABLE_FSK: - self.fsk_ldpc_buffer_1.push(x) + for audiobuffer, receive, index in [ + (self.datac0_buffer, True, 0), + (self.datac1_buffer, RECEIVE_DATAC1, 1), + (self.datac3_buffer, RECEIVE_DATAC3, 2), + (self.fsk_ldpc_buffer_0, static.ENABLE_FSK, 3), + (self.fsk_ldpc_buffer_1, static.ENABLE_FSK, 4), + ]: + if audiobuffer.nbuffer + length_x > audiobuffer.size: + static.BUFFER_OVERFLOW_COUNTER[index] += 1 + elif receive: + audiobuffer.push(x) if len(self.modoutqueue) <= 0 or self.mod_out_locked: # if not self.modoutqueue or self.mod_out_locked: @@ -451,7 +435,9 @@ class RF: # return (data_out48k, audio.pyaudio.paContinue) # -------------------------------------------------------------------- - def transmit(self, mode, repeats: int, repeat_delay: int, frames: bytearray): + def transmit( + self, mode, repeats: int, repeat_delay: int, frames: bytearray + ) -> None: """ Args: @@ -508,7 +494,10 @@ class RF: for _ in range(repeats): # codec2 fsk preamble may be broken - # at least it sounds like that, so we are disabling it for testing - if self.MODE not in ["FSK_LDPC_0", "FSK_LDPC_1", 200, 201]: + if self.MODE not in [ + codec2.FREEDV_MODE.fsk_ldpc_0.value, + codec2.FREEDV_MODE.fsk_ldpc_1.value, + ]: # Write preamble to txbuffer codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble) txbuffer += bytes(mod_out_preamble) @@ -540,7 +529,10 @@ class RF: # codec2 fsk postamble may be broken - # at least it sounds like that, so we are disabling it for testing - if self.MODE not in ["FSK_LDPC_0", "FSK_LDPC_1", 200, 201]: + if self.MODE not in [ + codec2.FREEDV_MODE.fsk_ldpc_0.value, + codec2.FREEDV_MODE.fsk_ldpc_1.value, + ]: # Write postamble to txbuffer codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble) # Append postamble to txbuffer @@ -604,7 +596,7 @@ class RF: freedv: ctypes.c_void_p, bytes_out, bytes_per_frame, - ): + ) -> int: """ De-modulate supplied audio stream with supplied codec2 instance. Decoded audio is placed into `bytes_out`. @@ -641,7 +633,7 @@ class RF: self.calculate_snr(freedv) return nin - def audio_datac0(self): + def audio_datac0(self) -> None: """Receive data encoded with datac0""" self.datac0_nin = self.demodulate_audio( self.datac0_buffer, @@ -651,7 +643,7 @@ class RF: self.datac0_bytes_per_frame, ) - def audio_datac1(self): + def audio_datac1(self) -> None: """Receive data encoded with datac1""" self.datac1_nin = self.demodulate_audio( self.datac1_buffer, @@ -661,7 +653,7 @@ class RF: self.datac1_bytes_per_frame, ) - def audio_datac3(self): + def audio_datac3(self) -> None: """Receive data encoded with datac3""" self.datac3_nin = self.demodulate_audio( self.datac3_buffer, @@ -671,7 +663,7 @@ class RF: self.datac3_bytes_per_frame, ) - def audio_fsk_ldpc_0(self): + def audio_fsk_ldpc_0(self) -> None: """Receive data encoded with FSK + LDPC0""" self.fsk_ldpc_nin_0 = self.demodulate_audio( self.fsk_ldpc_buffer_0, @@ -681,7 +673,7 @@ class RF: self.fsk_ldpc_bytes_per_frame_0, ) - def audio_fsk_ldpc_1(self): + def audio_fsk_ldpc_1(self) -> None: """Receive data encoded with FSK + LDPC1""" self.fsk_ldpc_nin_1 = self.demodulate_audio( self.fsk_ldpc_buffer_1, @@ -691,7 +683,7 @@ class RF: self.fsk_ldpc_bytes_per_frame_1, ) - def worker_transmit(self): + def worker_transmit(self) -> None: """Worker for FIFO queue for processing frames to be transmitted""" while True: data = self.modem_transmit_queue.get() @@ -704,7 +696,7 @@ class RF: ) # self.modem_transmit_queue.task_done() - def worker_received(self): + def worker_received(self) -> None: """Worker for FIFO queue for processing received frames""" while True: data = self.modem_received_queue.get() @@ -734,7 +726,7 @@ class RF: static.FREQ_OFFSET = offset return offset - def get_scatter(self, freedv: ctypes.c_void_p): + def get_scatter(self, freedv: ctypes.c_void_p) -> None: """ Ask codec2 for data about the received signal and calculate the scatter plot. Side-effect: sets static.SCATTER @@ -804,7 +796,7 @@ class RF: static.SNR = 0 return static.SNR - def update_rig_data(self): + def update_rig_data(self) -> None: """ Request information about the current state of the radio via hamlib Side-effect: sets @@ -818,7 +810,7 @@ class RF: static.HAMLIB_MODE = self.hamlib.get_mode() static.HAMLIB_BANDWIDTH = self.hamlib.get_bandwith() - def calculate_fft(self): + def calculate_fft(self) -> None: """ Calculate an average signal strength of the channel to assess whether the channel is 'busy.' @@ -886,7 +878,7 @@ class RF: # else 0 static.FFT = [0] - def set_frames_per_burst(self, frames_per_burst: int): + def set_frames_per_burst(self, frames_per_burst: int) -> None: """ Configure codec2 to send the configured number of frames per burst. @@ -902,7 +894,7 @@ class RF: codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, frames_per_burst) -def open_codec2_instance(mode: Union[int, str]) -> ctypes.c_void_p: +def open_codec2_instance(mode: int) -> ctypes.c_void_p: """ Return a codec2 instance of the type `mode` @@ -911,7 +903,7 @@ def open_codec2_instance(mode: Union[int, str]) -> ctypes.c_void_p: :return: C-function of the requested codec2 instance :rtype: ctypes.c_void_p """ - if mode in ["FSK_LDPC_0", 200]: + if mode in [codec2.FREEDV_MODE.fsk_ldpc_0.value]: return ctypes.cast( codec2.api.freedv_open_advanced( codec2.api.FREEDV_MODE_FSK_LDPC, @@ -920,7 +912,7 @@ def open_codec2_instance(mode: Union[int, str]) -> ctypes.c_void_p: ctypes.c_void_p, ) - if mode in ["FSK_LDPC_1", 201]: + if mode in [codec2.FREEDV_MODE.fsk_ldpc_1.value]: return ctypes.cast( codec2.api.freedv_open_advanced( codec2.api.FREEDV_MODE_FSK_LDPC, @@ -932,7 +924,7 @@ def open_codec2_instance(mode: Union[int, str]) -> ctypes.c_void_p: return ctypes.cast(codec2.api.freedv_open(mode), ctypes.c_void_p) -def get_bytes_per_frame(mode: Union[int, str]) -> int: +def get_bytes_per_frame(mode: int) -> int: """ Provide bytes per frame information for accessing from data handler @@ -947,13 +939,13 @@ def get_bytes_per_frame(mode: Union[int, str]) -> int: return int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8) -def set_audio_volume(datalist: np.int16, volume: float) -> np.int16: +def set_audio_volume(datalist, volume: float) -> np.int16: """ Scale values for the provided audio samples by volume, `volume` is clipped to the range of 0-100 :param datalist: Audio samples to scale - :type datalist: np.int16 + :type datalist: NDArray[np.int16] :param volume: Percentage (0-100) to scale samples :type volume: float :return: Scaled audio samples From a19bf69f40ce0c88bd3400e5069cb269fb1772bd Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Sun, 22 May 2022 16:28:55 -0400 Subject: [PATCH 05/26] Minor reworking of the source Pythonness. --- tnc/codec2.py | 223 ++++++++++++++++++++++++++++---------------------- 1 file changed, 124 insertions(+), 99 deletions(-) diff --git a/tnc/codec2.py b/tnc/codec2.py index adb9f025..fafb817e 100644 --- a/tnc/codec2.py +++ b/tnc/codec2.py @@ -1,8 +1,10 @@ -#!/usr/bin/env python3 +""" +Python interface to the C-language codec2 library. +""" # -*- coding: utf-8 -*- # pylint: disable=invalid-name, line-too-long, c-extension-no-member -# pylint: disable=import-outside-toplevel +# pylint: disable=import-outside-toplevel, attribute-defined-outside-init import ctypes import glob @@ -20,13 +22,14 @@ class FREEDV_MODE(Enum): """ Enumeration for codec2 modes and names """ + + allmodes = 255 + datac0 = 14 + datac1 = 10 + datac3 = 12 + fsk_ldpc = 9 fsk_ldpc_0 = 200 fsk_ldpc_1 = 201 - fsk_ldpc = 9 - datac0 = 14 - datac1 = 10 - datac3 = 12 - allmodes = 255 # Function for returning the mode value @@ -35,25 +38,27 @@ def freedv_get_mode_value_by_name(mode: str) -> int: Get the codec2 mode by entering its string Args: - mode: + mode: String representation of the codec2 mode. Returns: int """ - return FREEDV_MODE[mode].value + return FREEDV_MODE[mode.lower()].value + # Function for returning the mode name def freedv_get_mode_name_by_value(mode: int) -> str: """ - get the codec2 mode name as string + Get the codec2 mode name as string Args: - mode: + mode: Integer value of the codec2 mode. Returns: string """ return FREEDV_MODE(mode).name + # Check if we are running in a pyinstaller environment if hasattr(sys, "_MEIPASS"): sys.path.append(getattr(sys, "_MEIPASS")) @@ -61,13 +66,13 @@ else: sys.path.append(os.path.abspath(".")) structlog.get_logger("structlog").info("[C2 ] Searching for libcodec2...") -if sys.platform == 'linux': - files = glob.glob(r'**/*libcodec2*',recursive=True) - files.append('libcodec2.so') -elif sys.platform == 'darwin': - files = glob.glob(r'**/*libcodec2*.dylib',recursive=True) -elif sys.platform in ['win32', 'win64']: - files = glob.glob(r'**\*libcodec2*.dll',recursive=True) +if sys.platform == "linux": + files = glob.glob(r"**/*libcodec2*", recursive=True) + files.append("libcodec2.so") +elif sys.platform == "darwin": + files = glob.glob(r"**/*libcodec2*.dylib", recursive=True) +elif sys.platform in ["win32", "win64"]: + files = glob.glob(r"**\*libcodec2*.dll", recursive=True) else: files = [] @@ -77,78 +82,81 @@ for file in files: api = ctypes.CDLL(file) structlog.get_logger("structlog").info("[C2 ] Libcodec2 loaded", path=file) break - except Exception as e: - structlog.get_logger("structlog").warning("[C2 ] Libcodec2 found but not loaded", path=file, e=e) + except OSError as e: + structlog.get_logger("structlog").warning( + "[C2 ] Libcodec2 found but not loaded", path=file, e=e + ) # Quit module if codec2 cant be loaded -if api is None or 'api' not in locals(): +if api is None or "api" not in locals(): structlog.get_logger("structlog").critical("[C2 ] Libcodec2 not loaded") sys.exit(1) # ctypes function init -#api.freedv_set_tuning_range.restype = ctypes.c_int -#api.freedv_set_tuning_range.argype = [ctypes.c_void_p, ctypes.c_float, ctypes.c_float] +# api.freedv_set_tuning_range.restype = ctypes.c_int +# api.freedv_set_tuning_range.argype = [ctypes.c_void_p, ctypes.c_float, ctypes.c_float] -api.freedv_open.argype = [ctypes.c_int] +api.freedv_open.argype = [ctypes.c_int] # type: ignore api.freedv_open.restype = ctypes.c_void_p -api.freedv_open_advanced.argtype = [ctypes.c_int, ctypes.c_void_p] +api.freedv_open_advanced.argtype = [ctypes.c_int, ctypes.c_void_p] # type: ignore api.freedv_open_advanced.restype = ctypes.c_void_p -api.freedv_get_bits_per_modem_frame.argtype = [ctypes.c_void_p] +api.freedv_get_bits_per_modem_frame.argtype = [ctypes.c_void_p] # type: ignore api.freedv_get_bits_per_modem_frame.restype = ctypes.c_int -api.freedv_nin.argtype = [ctypes.c_void_p] +api.freedv_nin.argtype = [ctypes.c_void_p] # type: ignore api.freedv_nin.restype = ctypes.c_int -api.freedv_rawdatarx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] +api.freedv_rawdatarx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] # type: ignore api.freedv_rawdatarx.restype = ctypes.c_int -api.freedv_rawdatatx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] +api.freedv_rawdatatx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] # type: ignore api.freedv_rawdatatx.restype = ctypes.c_int -api.freedv_rawdatapostambletx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] +api.freedv_rawdatapostambletx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] # type: ignore api.freedv_rawdatapostambletx.restype = ctypes.c_int -api.freedv_rawdatapreambletx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] +api.freedv_rawdatapreambletx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] # type: ignore api.freedv_rawdatapreambletx.restype = ctypes.c_int -api.freedv_get_n_max_modem_samples.argtype = [ctypes.c_void_p] +api.freedv_get_n_max_modem_samples.argtype = [ctypes.c_void_p] # type: ignore api.freedv_get_n_max_modem_samples.restype = ctypes.c_int -api.freedv_set_frames_per_burst.argtype = [ctypes.c_void_p, ctypes.c_int] +api.freedv_set_frames_per_burst.argtype = [ctypes.c_void_p, ctypes.c_int] # type: ignore api.freedv_set_frames_per_burst.restype = ctypes.c_void_p -api.freedv_get_rx_status.argtype = [ctypes.c_void_p] +api.freedv_get_rx_status.argtype = [ctypes.c_void_p] # type: ignore api.freedv_get_rx_status.restype = ctypes.c_int -api.freedv_get_modem_stats.argtype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] +api.freedv_get_modem_stats.argtype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] # type: ignore api.freedv_get_modem_stats.restype = ctypes.c_int -api.freedv_get_n_tx_postamble_modem_samples.argtype = [ctypes.c_void_p] +api.freedv_get_n_tx_postamble_modem_samples.argtype = [ctypes.c_void_p] # type: ignore api.freedv_get_n_tx_postamble_modem_samples.restype = ctypes.c_int -api.freedv_get_n_tx_preamble_modem_samples.argtype = [ctypes.c_void_p] +api.freedv_get_n_tx_preamble_modem_samples.argtype = [ctypes.c_void_p] # type: ignore api.freedv_get_n_tx_preamble_modem_samples.restype = ctypes.c_int -api.freedv_get_n_tx_modem_samples.argtype = [ctypes.c_void_p] +api.freedv_get_n_tx_modem_samples.argtype = [ctypes.c_void_p] # type: ignore api.freedv_get_n_tx_modem_samples.restype = ctypes.c_int -api.freedv_get_n_max_modem_samples.argtype = [ctypes.c_void_p] +api.freedv_get_n_max_modem_samples.argtype = [ctypes.c_void_p] # type: ignore api.freedv_get_n_max_modem_samples.restype = ctypes.c_int -api.FREEDV_FS_8000 = 8000 -api.FREEDV_MODE_DATAC1 = 10 -api.FREEDV_MODE_DATAC3 = 12 -api.FREEDV_MODE_DATAC0 = 14 -api.FREEDV_MODE_FSK_LDPC = 9 +api.FREEDV_FS_8000 = 8000 # type: ignore +api.FREEDV_MODE_DATAC1 = 10 # type: ignore +api.FREEDV_MODE_DATAC3 = 12 # type: ignore +api.FREEDV_MODE_DATAC0 = 14 # type: ignore +api.FREEDV_MODE_FSK_LDPC = 9 # type: ignore # -------------------------------- FSK LDPC MODE SETTINGS -# Advanced structure for fsk modes + class ADVANCED(ctypes.Structure): - """ """ + """Advanced structure for fsk modes""" + _fields_ = [ ("interleave_frames", ctypes.c_int), ("M", ctypes.c_int), @@ -159,7 +167,9 @@ class ADVANCED(ctypes.Structure): ("codename", ctypes.c_char_p), ] -''' + +# pylint: disable=pointless-string-statement +""" adv.interleave_frames = 0 # max amplitude adv.M = 2 # number of fsk tones 2/4 adv.Rs = 100 # symbol rate @@ -179,43 +189,44 @@ H_128_256_5 rate 0.50 (256,128) BPF: 16 working H_4096_8192_3d rate 0.50 (8192,4096) BPF: 512 not working H_16200_9720 rate 0.60 (16200,9720) BPF: 1215 not working H_1024_2048_4f rate 0.50 (2048,1024) BPF: 128 working -''' +""" # --------------- 2 FSK H_128_256_5, 16 bytes -api.FREEDV_MODE_FSK_LDPC_0_ADV = ADVANCED() +api.FREEDV_MODE_FSK_LDPC_0_ADV = ADVANCED() # type: ignore api.FREEDV_MODE_FSK_LDPC_0_ADV.interleave_frames = 0 api.FREEDV_MODE_FSK_LDPC_0_ADV.M = 4 api.FREEDV_MODE_FSK_LDPC_0_ADV.Rs = 100 api.FREEDV_MODE_FSK_LDPC_0_ADV.Fs = 8000 -api.FREEDV_MODE_FSK_LDPC_0_ADV.first_tone = 1400 # 1150 4fsk, 1500 2fsk -api.FREEDV_MODE_FSK_LDPC_0_ADV.tone_spacing = 120 #200 -api.FREEDV_MODE_FSK_LDPC_0_ADV.codename = 'H_128_256_5'.encode('utf-8') # code word +api.FREEDV_MODE_FSK_LDPC_0_ADV.first_tone = 1400 # 1150 4fsk, 1500 2fsk +api.FREEDV_MODE_FSK_LDPC_0_ADV.tone_spacing = 120 # 200 +api.FREEDV_MODE_FSK_LDPC_0_ADV.codename = "H_128_256_5".encode("utf-8") # code word # --------------- 4 H_256_512_4, 7 bytes -api.FREEDV_MODE_FSK_LDPC_1_ADV = ADVANCED() +api.FREEDV_MODE_FSK_LDPC_1_ADV = ADVANCED() # type: ignore api.FREEDV_MODE_FSK_LDPC_1_ADV.interleave_frames = 0 api.FREEDV_MODE_FSK_LDPC_1_ADV.M = 4 api.FREEDV_MODE_FSK_LDPC_1_ADV.Rs = 100 api.FREEDV_MODE_FSK_LDPC_1_ADV.Fs = 8000 -api.FREEDV_MODE_FSK_LDPC_1_ADV.first_tone = 1250 # 1250 4fsk, 1500 2fsk +api.FREEDV_MODE_FSK_LDPC_1_ADV.first_tone = 1250 # 1250 4fsk, 1500 2fsk api.FREEDV_MODE_FSK_LDPC_1_ADV.tone_spacing = 200 -api.FREEDV_MODE_FSK_LDPC_1_ADV.codename = 'H_256_512_4'.encode('utf-8') # code word +api.FREEDV_MODE_FSK_LDPC_1_ADV.codename = "H_256_512_4".encode("utf-8") # code word # ------- MODEM STATS STRUCTURES -MODEM_STATS_NC_MAX = 50 + 1 -MODEM_STATS_NR_MAX = 160 -MODEM_STATS_ET_MAX = 8 -MODEM_STATS_EYE_IND_MAX = 160 -MODEM_STATS_NSPEC = 512 -MODEM_STATS_MAX_F_HZ = 4000 -MODEM_STATS_MAX_F_EST = 4 +MODEM_STATS_NC_MAX = 50 + 1 +MODEM_STATS_NR_MAX = 160 +MODEM_STATS_ET_MAX = 8 +MODEM_STATS_EYE_IND_MAX = 160 +MODEM_STATS_NSPEC = 512 +MODEM_STATS_MAX_F_HZ = 4000 +MODEM_STATS_MAX_F_EST = 4 + -# Modem stats structure class MODEMSTATS(ctypes.Structure): - """ """ + """Modem statistics structure""" + _fields_ = [ ("Nc", ctypes.c_int), ("snr_est", ctypes.c_float), - ("rx_symbols", (ctypes.c_float * MODEM_STATS_NR_MAX)*MODEM_STATS_NC_MAX), + ("rx_symbols", (ctypes.c_float * MODEM_STATS_NR_MAX) * MODEM_STATS_NC_MAX), ("nr", ctypes.c_int), ("sync", ctypes.c_int), ("foff", ctypes.c_float), @@ -225,19 +236,23 @@ class MODEMSTATS(ctypes.Structure): ("pre", ctypes.c_int), ("post", ctypes.c_int), ("uw_fails", ctypes.c_int), - ("neyetr", ctypes.c_int), # How many eye traces are plotted - ("neyesamp", ctypes.c_int), # How many samples in the eye diagram - ("f_est", (ctypes.c_float * MODEM_STATS_MAX_F_EST)), # How many samples in the eye diagram + ("neyetr", ctypes.c_int), # How many eye traces are plotted + ("neyesamp", ctypes.c_int), # How many samples in the eye diagram + ( + "f_est", + (ctypes.c_float * MODEM_STATS_MAX_F_EST), + ), # How many samples in the eye diagram ("fft_buf", (ctypes.c_float * MODEM_STATS_NSPEC * 2)), ] -# Return code flags for freedv_get_rx_status() function -api.FREEDV_RX_TRIAL_SYNC = 0x1 # demodulator has trial sync -api.FREEDV_RX_SYNC = 0x2 # demodulator has sync -api.FREEDV_RX_BITS = 0x4 # data bits have been returned -api.FREEDV_RX_BIT_ERRORS = 0x8 # FEC may not have corrected all bit errors (not all parity checks OK) -api.rx_sync_flags_to_text = [ +# Return code flags for freedv_get_rx_status() function +api.FREEDV_RX_TRIAL_SYNC = 0x1 # type: ignore # demodulator has trial sync +api.FREEDV_RX_SYNC = 0x2 # type: ignore # demodulator has sync +api.FREEDV_RX_BITS = 0x4 # type: ignore # data bits have been returned +api.FREEDV_RX_BIT_ERRORS = 0x8 # type: ignore # FEC may not have corrected all bit errors (not all parity checks OK) + +api.rx_sync_flags_to_text = [ # type: ignore "----", "---T", "--S-", @@ -253,7 +268,8 @@ api.rx_sync_flags_to_text = [ "EB--", "EB-T", "EBS-", - "EBST"] + "EBST", +] # Audio buffer --------------------------------------------------------- class audio_buffer: @@ -262,16 +278,19 @@ class audio_buffer: made by David Rowe, VK5DGR """ + # A buffer of int16 samples, using a fixed length numpy array self.buffer for storage # self.nbuffer is the current number of samples in the buffer def __init__(self, size): - structlog.get_logger("structlog").debug("[C2 ] Creating audio buffer", size=size) + structlog.get_logger("structlog").debug( + "[C2 ] Creating audio buffer", size=size + ) self.size = size self.buffer = np.zeros(size, dtype=np.int16) self.nbuffer = 0 self.mutex = Lock() - def push(self,samples): + def push(self, samples): """ Push new data to buffer @@ -283,12 +302,12 @@ class audio_buffer: """ self.mutex.acquire() # Add samples at the end of the buffer - assert self.nbuffer+len(samples) <= self.size - self.buffer[self.nbuffer:self.nbuffer+len(samples)] = samples + assert self.nbuffer + len(samples) <= self.size + self.buffer[self.nbuffer : self.nbuffer + len(samples)] = samples self.nbuffer += len(samples) self.mutex.release() - def pop(self,size): + def pop(self, size): """ get data from buffer in size of NIN Args: @@ -300,22 +319,28 @@ class audio_buffer: self.mutex.acquire() # Remove samples from the start of the buffer self.nbuffer -= size - self.buffer[:self.nbuffer] = self.buffer[size:size+self.nbuffer] + self.buffer[: self.nbuffer] = self.buffer[size : size + self.nbuffer] assert self.nbuffer >= 0 self.mutex.release() + # Resampler --------------------------------------------------------- -api.FDMDV_OS_48 = int(6) # oversampling rate -api.FDMDV_OS_TAPS_48K = int(48) # number of OS filter taps at 48kHz -api.FDMDV_OS_TAPS_48_8K = int(api.FDMDV_OS_TAPS_48K/api.FDMDV_OS_48) # number of OS filter taps at 8kHz -api.fdmdv_8_to_48_short.argtype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int] -api.fdmdv_48_to_8_short.argtype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int] +# Oversampling rate +api.FDMDV_OS_48 = 6 # type: ignore +# Number of oversampling taps at 48kHz +api.FDMDV_OS_TAPS_48K = 48 # type: ignore +# Number of oversampling filter taps at 8kHz +api.FDMDV_OS_TAPS_48_8K = api.FDMDV_OS_TAPS_48K // api.FDMDV_OS_48 # type: ignore +api.fdmdv_8_to_48_short.argtype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int] # type: ignore +api.fdmdv_48_to_8_short.argtype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int] # type: ignore + class resampler: """ Re-sampler class """ + # Re-sample an array of variable length, we just store the filter memories here MEM8 = api.FDMDV_OS_TAPS_48_8K MEM48 = api.FDMDV_OS_TAPS_48K @@ -337,21 +362,21 @@ class resampler: """ assert in48.dtype == np.int16 # Length of input vector must be an integer multiple of api.FDMDV_OS_48 - assert len(in48) % api.FDMDV_OS_48 == 0 + assert len(in48) % api.FDMDV_OS_48 == 0 # type: ignore # Concatenate filter memory and input samples - in48_mem = np.zeros(self.MEM48+len(in48), dtype=np.int16) - in48_mem[:self.MEM48] = self.filter_mem48 - in48_mem[self.MEM48:] = in48 + in48_mem = np.zeros(self.MEM48 + len(in48), dtype=np.int16) + in48_mem[: self.MEM48] = self.filter_mem48 + in48_mem[self.MEM48 :] = in48 # In C: pin48=&in48_mem[MEM48] pin48 = ctypes.byref(np.ctypeslib.as_ctypes(in48_mem), 2 * self.MEM48) - n8 = int(len(in48) / api.FDMDV_OS_48) + n8 = int(len(in48) / api.FDMDV_OS_48) # type: ignore out8 = np.zeros(n8, dtype=np.int16) - api.fdmdv_48_to_8_short(out8.ctypes, pin48, n8) + api.fdmdv_48_to_8_short(out8.ctypes, pin48, n8) # type: ignore # Store memory for next time - self.filter_mem48 = in48_mem[:self.MEM48] + self.filter_mem48 = in48_mem[: self.MEM48] return out8 @@ -368,16 +393,16 @@ class resampler: assert in8.dtype == np.int16 # Concatenate filter memory and input samples - in8_mem = np.zeros(self.MEM8+len(in8), dtype=np.int16) - in8_mem[:self.MEM8] = self.filter_mem8 - in8_mem[self.MEM8:] = in8 + in8_mem = np.zeros(self.MEM8 + len(in8), dtype=np.int16) + in8_mem[: self.MEM8] = self.filter_mem8 + in8_mem[self.MEM8 :] = in8 # In C: pin8=&in8_mem[MEM8] pin8 = ctypes.byref(np.ctypeslib.as_ctypes(in8_mem), 2 * self.MEM8) - out48 = np.zeros(api.FDMDV_OS_48*len(in8), dtype=np.int16) - api.fdmdv_8_to_48_short(out48.ctypes, pin8, len(in8)); + out48 = np.zeros(api.FDMDV_OS_48 * len(in8), dtype=np.int16) # type: ignore + api.fdmdv_8_to_48_short(out48.ctypes, pin8, len(in8)) # type: ignore # Store memory for next time - self.filter_mem8 = in8_mem[:self.MEM8] + self.filter_mem8 = in8_mem[: self.MEM8] return out48 From 2c0728770cfcd45d16832d54f09a8dd18a006930 Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Sun, 22 May 2022 20:54:12 -0400 Subject: [PATCH 06/26] Collapse repeated code to a function. Add function parameter and return types. Move logging to a class variable. Extended use of FREEDV_MODE enum. Moved TESTING check to arq_cleanup. --- tnc/data_handler.py | 1715 +++++++++++++++++++++++++++---------------- 1 file changed, 1096 insertions(+), 619 deletions(-) diff --git a/tnc/data_handler.py b/tnc/data_handler.py index fb8b33c2..1696acb5 100644 --- a/tnc/data_handler.py +++ b/tnc/data_handler.py @@ -1,12 +1,11 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- +# -*- coding: UTF-8 -*- """ Created on Sun Dec 27 20:43:40 2020 @author: DJ2LS """ # pylint: disable=invalid-name, line-too-long, c-extension-no-member -# pylint: disable=import-outside-toplevel +# pylint: disable=import-outside-toplevel, attribute-defined-outside-init import base64 import queue @@ -17,15 +16,14 @@ import uuid import zlib from random import randrange -import numpy as np -import structlog -import ujson as json - import codec2 import helpers import modem +import numpy as np import sock import static +import structlog +import ujson as json TESTMODE = False @@ -34,10 +32,13 @@ DATA_QUEUE_RECEIVED = queue.Queue() class DATA: - """ Terminal Node Controller for FreeDATA """ + """Terminal Node Controller for FreeDATA""" - def __init__(self): - self.mycallsign = static.MYCALLSIGN # initial call sign. Will be overwritten later + log = structlog.get_logger(__name__) + + def __init__(self) -> None: + # Initial call sign. Will be overwritten later + self.mycallsign = static.MYCALLSIGN self.data_queue_transmit = DATA_QUEUE_TRANSMIT self.data_queue_received = DATA_QUEUE_RECEIVED @@ -49,42 +50,67 @@ class DATA: self.arq_session_timeout = 30 self.session_connect_max_retries = 3 - self.transmission_uuid = '' + self.transmission_uuid = "" - self.received_mycall_crc = b'' # Received my callsign crc if we received a crc for another ssid + # Received my callsign crc if we received a crc for another ssid + self.received_mycall_crc = b"" self.data_channel_last_received = 0.0 # time of last "live sign" of a frame - self.burst_ack_snr = 0 # SNR from received ack frames - self.burst_ack = False # if we received an acknowledge frame for a burst - self.data_frame_ack_received = False # if we received an acknowledge frame for a data frame - self.rpt_request_received = False # if we received an request for repeater frames + self.burst_ack_snr = 0 # SNR from received burst ack frames + + # Flag to indicate if we received an acknowledge frame for a burst + self.burst_ack = False + # Flag to indicate if we received an acknowledge frame for a data frame + self.data_frame_ack_received = False + # Flag to indicate if we received an request for repeater frames + self.rpt_request_received = False self.rpt_request_buffer = [] # requested frames, saved in a list self.rx_start_of_transmission = 0 # time of transmission start - self.data_frame_bof = b'BOF' # 2 bytes for the BOF End of File indicator in a data frame - self.data_frame_eof = b'EOF' # 2 bytes for the EOF End of File indicator in a data frame + + # 3 bytes for the BOF Beginning of File indicator in a data frame + self.data_frame_bof = b"BOF" + # 3 bytes for the EOF End of File indicator in a data frame + self.data_frame_eof = b"EOF" self.rx_n_max_retries_per_burst = 50 self.n_retries_per_burst = 0 - self.received_low_bandwith_mode = False # indicator if we recevied a low bandwith mode channel opener + # Flag to indicate if we recevied a low bandwith mode channel opener + self.received_low_bandwith_mode = False self.data_channel_max_retries = 5 self.datachannel_timeout = False - self.mode_list_low_bw = [14, 12] + # List of codec2 modes to use in 'low bandwidth' mode. + self.mode_list_low_bw = [ + codec2.FREEDV_MODE.datac0.value, + codec2.FREEDV_MODE.datac3.value, + ] + # List for time to wait for corresponding mode in seconds self.time_list_low_bw = [3, 7] - self.mode_list_high_bw = [14, 12, 10] # mode list of available modes,each mode will be used 2 times per level - self.time_list_high_bw = [3, 7, 8, 30] # list for time to wait for corresponding mode in seconds + # List of codec2 modes to use in 'high bandwidth' mode. + self.mode_list_high_bw = [ + codec2.FREEDV_MODE.datac0.value, + codec2.FREEDV_MODE.datac3.value, + codec2.FREEDV_MODE.datac1.value, + ] + # List for time to wait for corresponding mode in seconds + self.time_list_high_bw = [3, 7, 8, 30] - # mode list for selecting between low bandwidth ( 500Hz ) and normal modes with higher bandwidth + # Mode list for selecting between low bandwidth ( 500Hz ) and modes with higher bandwidth + # but ability to fall back to low bandwidth modes if needed. if static.LOW_BANDWITH_MODE: - self.mode_list = self.mode_list_low_bw # mode list of available modes, each mode will be used 2times per speed level - self.time_list = self.time_list_low_bw # list for time to wait for corresponding mode in seconds + # List of codec2 modes to use in 'low bandwidth' mode. + self.mode_list = self.mode_list_low_bw + # list of times to wait for corresponding mode in seconds + self.time_list = self.time_list_low_bw else: - self.mode_list = self.mode_list_high_bw # mode list of available modes, each mode will be used 2times per speed level - self.time_list = self.time_list_high_bw # list for time to wait for corresponding mode in seconds + # List of codec2 modes to use in 'high bandwidth' mode. + self.mode_list = self.mode_list_high_bw + # list of times to wait for corresponding mode in seconds + self.time_list = self.time_list_high_bw self.speed_level = len(self.mode_list) - 1 # speed level for selecting mode static.ARQ_SPEED_LEVEL = self.speed_level @@ -99,41 +125,51 @@ class DATA: self.transmission_timeout = 360 # transmission timeout in seconds - worker_thread_transmit = threading.Thread(target=self.worker_transmit, name="worker thread transmit", - daemon=True) + # Start worker and watchdog threads + worker_thread_transmit = threading.Thread( + target=self.worker_transmit, name="worker thread transmit", daemon=True + ) worker_thread_transmit.start() - worker_thread_receive = threading.Thread(target=self.worker_receive, name="worker thread receive", daemon=True) + worker_thread_receive = threading.Thread( + target=self.worker_receive, name="worker thread receive", daemon=True + ) worker_thread_receive.start() # START THE THREAD FOR THE TIMEOUT WATCHDOG - watchdog_thread = threading.Thread(target=self.watchdog, name="watchdog", daemon=True) + watchdog_thread = threading.Thread( + target=self.watchdog, name="watchdog", daemon=True + ) watchdog_thread.start() - arq_session_thread = threading.Thread(target=self.heartbeat, name="watchdog", daemon=True) + arq_session_thread = threading.Thread( + target=self.heartbeat, name="watchdog", daemon=True + ) arq_session_thread.start() self.beacon_interval = 0 - self.beacon_thread = threading.Thread(target=self.run_beacon, name="watchdog", daemon=True) + self.beacon_thread = threading.Thread( + target=self.run_beacon, name="watchdog", daemon=True + ) self.beacon_thread.start() - def worker_transmit(self): - """ """ + def worker_transmit(self) -> None: + """Dispatch incoming UI instructions for transmitting operations""" while True: data = self.data_queue_transmit.get() # [0] == Command - if data[0] == 'CQ': + if data[0] == "CQ": self.transmit_cq() - elif data[0] == 'STOP': + elif data[0] == "STOP": self.stop_transmission() - elif data[0] == 'PING': + elif data[0] == "PING": # [1] dxcallsign self.transmit_ping(data[1]) - elif data[0] == 'BEACON': + elif data[0] == "BEACON": # [1] INTERVAL int # [2] STATE bool if data[2]: @@ -142,7 +178,7 @@ class DATA: else: static.BEACON_STATE = False - elif data[0] == 'ARQ_RAW': + elif data[0] == "ARQ_RAW": # [1] DATA_OUT bytes # [2] MODE int # [3] N_FRAMES_PER_BURST int @@ -150,32 +186,37 @@ class DATA: # [5] mycallsign with ssid self.open_dc_and_transmit(data[1], data[2], data[3], data[4], data[5]) - elif data[0] == 'CONNECT': + elif data[0] == "CONNECT": # [1] DX CALLSIGN - self.arq_session_handler(data[1]) + # self.arq_session_handler(data[1]) + self.arq_session_handler() - elif data[0] == 'DISCONNECT': + elif data[0] == "DISCONNECT": # [1] DX CALLSIGN self.close_session() - elif data[0] == 'SEND_TEST_FRAME': + elif data[0] == "SEND_TEST_FRAME": # [1] DX CALLSIGN self.send_test_frame() else: - structlog.get_logger("structlog").error("[TNC] worker_transmit: received invalid command:", data=data) - # print(f"Invalid command: {data}") + self.log.error( + "[TNC] worker_transmit: received invalid command:", data=data + ) - def worker_receive(self): - """ """ + def worker_receive(self) -> None: + """Queue received data for processing""" while True: data = self.data_queue_received.get() # [0] bytes # [1] freedv instance # [2] bytes_per_frame - self.process_data(bytes_out=data[0], freedv=data[1], bytes_per_frame=data[2]) + self.process_data( + bytes_out=data[0], freedv=data[1], bytes_per_frame=data[2] + ) - def process_data(self, bytes_out, freedv, bytes_per_frame: int): + def process_data(self, bytes_out, freedv, bytes_per_frame: int) -> None: """ + Process incoming data and decide what to do with the frame. Args: bytes_out: @@ -185,10 +226,11 @@ class DATA: Returns: """ - structlog.get_logger("structlog").debug("[TNC] process_data:", n_retries_per_burst=self.n_retries_per_burst) - # print(f"self.n_retries_per_burst = {self.n_retries_per_burst}") + self.log.debug( + "[TNC] process_data:", n_retries_per_burst=self.n_retries_per_burst + ) - # forward data only if broadcast or we are the receiver + # Process data only if broadcast or we are the receiver # bytes_out[1:4] == callsign check for signalling frames, # bytes_out[2:5] == transmission # we could also create an own function, which returns True. @@ -203,141 +245,149 @@ class DATA: if 50 >= frametype >= 10: # get snr of received data + # FIXME: find a fix for this - after moving to classes, this no longer works # snr = self.calculate_snr(freedv) - # we need to find a way of fixing this because after moving to class system this doesn't work anymore snr = static.SNR - structlog.get_logger("structlog").debug("[TNC] RX SNR", snr=snr) + self.log.debug("[TNC] RX SNR", snr=snr) # send payload data to arq checker without CRC16 - self.arq_data_received(bytes(bytes_out[:-2]), bytes_per_frame, snr, freedv) + self.arq_data_received( + bytes(bytes_out[:-2]), bytes_per_frame, snr, freedv + ) # if we received the last frame of a burst or the last remaining rpt frame, do a modem unsync # if static.RX_BURST_BUFFER.count(None) <= 1 or (frame+1) == n_frames_per_burst: - # structlog.get_logger("structlog").debug(f"[TNC] LAST FRAME OF BURST --> UNSYNC {frame+1}/{n_frames_per_burst}") + # self.log.debug(f"[TNC] LAST FRAME OF BURST --> UNSYNC {frame+1}/{n_frames_per_burst}") # self.c_lib.freedv_set_sync(freedv, 0) # BURST ACK elif frametype == 60: - structlog.get_logger("structlog").debug("[TNC] ACK RECEIVED....") + self.log.debug("[TNC] ACK RECEIVED....") self.burst_ack_received(bytes_out[:-2]) # FRAME ACK elif frametype == 61: - structlog.get_logger("structlog").debug("[TNC] FRAME ACK RECEIVED....") + self.log.debug("[TNC] FRAME ACK RECEIVED....") self.frame_ack_received() # FRAME RPT elif frametype == 62: - structlog.get_logger("structlog").debug("[TNC] REPEAT REQUEST RECEIVED....") + self.log.debug("[TNC] REPEAT REQUEST RECEIVED....") self.burst_rpt_received(bytes_out[:-2]) # FRAME NACK elif frametype == 63: - structlog.get_logger("structlog").debug("[TNC] FRAME NACK RECEIVED....") + self.log.debug("[TNC] FRAME NACK RECEIVED....") self.frame_nack_received(bytes_out[:-2]) # BURST NACK elif frametype == 64: - structlog.get_logger("structlog").debug("[TNC] BURST NACK RECEIVED....") + self.log.debug("[TNC] BURST NACK RECEIVED....") self.burst_nack_received(bytes_out[:-2]) # CQ FRAME elif frametype == 200: - structlog.get_logger("structlog").debug("[TNC] CQ RECEIVED....") + self.log.debug("[TNC] CQ RECEIVED....") self.received_cq(bytes_out[:-2]) # QRV FRAME elif frametype == 201: - structlog.get_logger("structlog").debug("[TNC] QRV RECEIVED....") + self.log.debug("[TNC] QRV RECEIVED....") self.received_qrv(bytes_out[:-2]) # PING FRAME elif frametype == 210: - structlog.get_logger("structlog").debug("[TNC] PING RECEIVED....") + self.log.debug("[TNC] PING RECEIVED....") self.received_ping(bytes_out[:-2]) # PING ACK elif frametype == 211: - structlog.get_logger("structlog").debug("[TNC] PING ACK RECEIVED....") + self.log.debug("[TNC] PING ACK RECEIVED....") self.received_ping_ack(bytes_out[:-2]) # SESSION OPENER elif frametype == 221: - structlog.get_logger("structlog").debug("[TNC] OPEN SESSION RECEIVED....") + self.log.debug("[TNC] OPEN SESSION RECEIVED....") self.received_session_opener(bytes_out[:-2]) # SESSION HEARTBEAT elif frametype == 222: - structlog.get_logger("structlog").debug("[TNC] SESSION HEARTBEAT RECEIVED....") + self.log.debug("[TNC] SESSION HEARTBEAT RECEIVED....") self.received_session_heartbeat(bytes_out[:-2]) # SESSION CLOSE elif frametype == 223: - structlog.get_logger("structlog").debug("[TNC] CLOSE ARQ SESSION RECEIVED....") + self.log.debug("[TNC] CLOSE ARQ SESSION RECEIVED....") self.received_session_close(bytes_out[:-2]) # ARQ FILE TRANSFER RECEIVED! elif frametype in [225, 227]: - structlog.get_logger("structlog").debug("[TNC] ARQ arq_received_data_channel_opener") + self.log.debug("[TNC] ARQ arq_received_data_channel_opener") self.arq_received_data_channel_opener(bytes_out[:-2]) # ARQ CHANNEL IS OPENED elif frametype in [226, 228]: - structlog.get_logger("structlog").debug("[TNC] ARQ arq_received_channel_is_open") + self.log.debug("[TNC] ARQ arq_received_channel_is_open") self.arq_received_channel_is_open(bytes_out[:-2]) # ARQ MANUAL MODE TRANSMISSION elif 230 <= frametype <= 240: - structlog.get_logger("structlog").debug("[TNC] ARQ manual mode") + self.log.debug("[TNC] ARQ manual mode") self.arq_received_data_channel_opener(bytes_out[:-2]) # ARQ STOP TRANSMISSION elif frametype == 249: - structlog.get_logger("structlog").debug("[TNC] ARQ received stop transmission") + self.log.debug("[TNC] ARQ received stop transmission") self.received_stop_transmission() # this is outdated and we may remove it elif frametype == 250: - structlog.get_logger("structlog").debug("[TNC] BEACON RECEIVED") + self.log.debug("[TNC] BEACON RECEIVED") self.received_beacon(bytes_out[:-2]) # TESTFRAMES elif frametype == 255: - structlog.get_logger("structlog").debug("[TNC] TESTFRAME RECEIVED", frame=bytes_out[:]) + self.log.debug("[TNC] TESTFRAME RECEIVED", frame=bytes_out[:]) # Unknown frame type else: - structlog.get_logger("structlog").warning("[TNC] ARQ - other frame type", frametype=frametype) + self.log.warning("[TNC] ARQ - other frame type", frametype=frametype) else: # for debugging purposes to receive all data - structlog.get_logger("structlog").debug("[TNC] Unknown frame received", frame=bytes_out[:-2]) + self.log.debug("[TNC] Unknown frame received", frame=bytes_out[:-2]) - def enqueue_frame_for_tx(self, frame_to_tx: bytearray, c2_mode=14, copies=1, repeat_delay=0): + def enqueue_frame_for_tx( + self, + frame_to_tx: bytearray, + c2_mode=codec2.FREEDV_MODE.datac0.value, + copies=1, + repeat_delay=0, + ) -> None: """ Send (transmit) supplied frame to TNC :param frame_to_tx: Frame data to send :type frame_to_tx: bytearray - :param c2_mode: Codec2 mode to use, defaults to "datac0" (14) - :type c2_mode: str, optional + :param c2_mode: Codec2 mode to use, defaults to 14 (datac0) + :type c2_mode: int, optional :param copies: Number of frame copies to send, defaults to 1 :type copies: int, optional :param repeat_delay: Delay time before sending repeat frame, defaults to 0 :type repeat_delay: int, optional """ - structlog.get_logger("structlog").debug("[TNC] enqueue_frame_for_tx", c2_mode=c2_mode) - if isinstance(c2_mode, str): - _mode = codec2.freedv_get_mode_value_by_name(c2_mode.lower()) - else: - _mode = int(c2_mode) + self.log.debug("[TNC] enqueue_frame_for_tx", c2_mode=c2_mode) + + # Set the TRANSMITTING flag before adding an object to the transmit queue + # TODO: This is not that nice, we could improve this somehow static.TRANSMITTING = True - modem.MODEM_TRANSMIT_QUEUE.put([_mode, copies, repeat_delay, [frame_to_tx]]) + modem.MODEM_TRANSMIT_QUEUE.put([c2_mode, copies, repeat_delay, [frame_to_tx]]) + # Wait while transmitting while static.TRANSMITTING: time.sleep(0.01) - def send_burst_ack_frame(self, snr): - """ Build and send ACK frame for burst DATA frame """ + def send_burst_ack_frame(self, snr) -> None: + """Build and send ACK frame for burst DATA frame""" ack_frame = bytearray(14) ack_frame[:1] = bytes([60]) ack_frame[1:4] = static.DXCALLSIGN_CRC @@ -348,8 +398,8 @@ class DATA: # Transmit frame self.enqueue_frame_for_tx(ack_frame) - def send_data_ack_frame(self, snr): - """ Build and send ACK frame for received DATA frame """ + def send_data_ack_frame(self, snr) -> None: + """Build and send ACK frame for received DATA frame""" ack_frame = bytearray(14) ack_frame[:1] = bytes([61]) ack_frame[1:4] = static.DXCALLSIGN_CRC @@ -360,9 +410,13 @@ class DATA: # Transmit frame self.enqueue_frame_for_tx(ack_frame, copies=3, repeat_delay=100) - def send_retransmit_request_frame(self, freedv): + def send_retransmit_request_frame(self, freedv) -> None: # check where a None is in our burst buffer and do frame+1, beacuse lists start at 0 - missing_frames = [frame + 1 for frame, element in enumerate(static.RX_BURST_BUFFER) if element is None] + missing_frames = [ + frame + 1 + for frame, element in enumerate(static.RX_BURST_BUFFER) + if element is None + ] # set n frames per burst to modem # this is an idea so its not getting lost.... @@ -378,12 +432,12 @@ class DATA: rpt_frame[4:7] = static.MYCALLSIGN_CRC rpt_frame[7:13] = missing_frames - structlog.get_logger("structlog").info("[TNC] ARQ | RX | Requesting", frames=missing_frames) + self.log.info("[TNC] ARQ | RX | Requesting", frames=missing_frames) # Transmit frame self.enqueue_frame_for_tx(rpt_frame) - def send_burst_nack_frame(self, snr=0): - """ Build and send NACK frame for received DATA frame """ + def send_burst_nack_frame(self, snr=0) -> None: + """Build and send NACK frame for received DATA frame""" nack_frame = bytearray(14) nack_frame[:1] = bytes([63]) nack_frame[1:4] = static.DXCALLSIGN_CRC @@ -394,8 +448,8 @@ class DATA: # TRANSMIT NACK FRAME FOR BURST self.enqueue_frame_for_tx(nack_frame) - def send_burst_nack_frame_watchdog(self, snr=0): - """ Build and send NACK frame for watchdog timeout """ + def send_burst_nack_frame_watchdog(self, snr=0) -> None: + """Build and send NACK frame for watchdog timeout""" nack_frame = bytearray(14) nack_frame[:1] = bytes([64]) nack_frame[1:4] = static.DXCALLSIGN_CRC @@ -406,8 +460,8 @@ class DATA: # TRANSMIT NACK FRAME FOR BURST self.enqueue_frame_for_tx(nack_frame) - def send_disconnect_frame(self): - """ Build and send a disconnect frame """ + def send_disconnect_frame(self) -> None: + """Build and send a disconnect frame""" disconnection_frame = bytearray(14) disconnection_frame[:1] = bytes([223]) disconnection_frame[1:4] = static.DXCALLSIGN_CRC @@ -416,7 +470,9 @@ class DATA: self.enqueue_frame_for_tx(disconnection_frame, copies=5, repeat_delay=250) - def arq_data_received(self, data_in: bytes, bytes_per_frame: int, snr: int, freedv): + def arq_data_received( + self, data_in: bytes, bytes_per_frame: int, snr: int, freedv + ) -> None: """ Args: data_in:bytes: @@ -432,27 +488,32 @@ class DATA: self.received_mycall_crc = data_in[2:5] # check if callsign ssid override - valid, mycallsign = helpers.check_callsign(self.mycallsign, self.received_mycall_crc) - if not valid: + _valid, mycallsign = helpers.check_callsign( + self.mycallsign, self.received_mycall_crc + ) + if not _valid: # ARQ data packet not for me. - if not TESTMODE: - self.arq_cleanup() + self.arq_cleanup() return # only process data if we are in ARQ and BUSY state else return to quit - if not static.ARQ_STATE and static.TNC_STATE != 'BUSY': + if not static.ARQ_STATE and static.TNC_STATE != "BUSY": return self.arq_file_transfer = True - static.TNC_STATE = 'BUSY' + static.TNC_STATE = "BUSY" static.ARQ_STATE = True static.INFO.append("ARQ;RECEIVING") + + # Update the timeout timestamp self.data_channel_last_received = int(time.time()) - # get some important data from the frame - RX_N_FRAME_OF_BURST = int.from_bytes(bytes(data_in[:1]), "big") - 10 # get number of burst frame - RX_N_FRAMES_PER_BURST = int.from_bytes(bytes(data_in[1:2]), "big") # get number of bursts from received frame + # Extract some important data from the frame + # Get sequence number of burst frame + RX_N_FRAME_OF_BURST = int.from_bytes(bytes(data_in[:1]), "big") - 10 + # Get number of bursts from received frame + RX_N_FRAMES_PER_BURST = int.from_bytes(bytes(data_in[1:2]), "big") # The RX burst buffer needs to have a fixed length filled with "None". # We need this later for counting the "Nones" to detect missing data. @@ -461,27 +522,37 @@ class DATA: static.RX_BURST_BUFFER = [None] * RX_N_FRAMES_PER_BURST # Append data to rx burst buffer - static.RX_BURST_BUFFER[RX_N_FRAME_OF_BURST] = data_in[8:] # [frame_type][n_frames_per_burst][CRC24][CRC24] + # [frame_type][n_frames_per_burst][CRC24][CRC24] + static.RX_BURST_BUFFER[RX_N_FRAME_OF_BURST] = data_in[8:] # type: ignore - structlog.get_logger("structlog").debug("[TNC] static.RX_BURST_BUFFER", buffer=static.RX_BURST_BUFFER) + self.log.debug("[TNC] static.RX_BURST_BUFFER", buffer=static.RX_BURST_BUFFER) - helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', snr, static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY) + helpers.add_to_heard_stations( + static.DXCALLSIGN, + static.DXGRID, + "DATA-CHANNEL", + snr, + static.FREQ_OFFSET, + static.HAMLIB_FREQUENCY, + ) # Check if we received all frames in the burst by checking if burst buffer has no more "Nones" # This is the ideal case because we received all data if None not in static.RX_BURST_BUFFER: # then iterate through burst buffer and stick the burst together # the temp burst buffer is needed for checking, if we already recevied data - temp_burst_buffer = b'' + temp_burst_buffer = b"" for value in static.RX_BURST_BUFFER: # static.RX_FRAME_BUFFER += static.RX_BURST_BUFFER[i] - temp_burst_buffer += bytes(value) + temp_burst_buffer += bytes(value) # type: ignore # if frame buffer ends not with the current frame, we are going to append new data - # if data already exists, we received the frame correctly, but the ACK frame didnt receive its destination (ISS) + # if data already exists, we received the frame correctly, + # but the ACK frame didn't receive its destination (ISS) if static.RX_FRAME_BUFFER.endswith(temp_burst_buffer): - structlog.get_logger("structlog").info("[TNC] ARQ | RX | Frame already received - sending ACK again") + self.log.info( + "[TNC] ARQ | RX | Frame already received - sending ACK again" + ) static.RX_BURST_BUFFER = [] else: @@ -495,23 +566,34 @@ class DATA: search_position = len(static.RX_FRAME_BUFFER) - search_area # find position of data. returns -1 if nothing found in area else >= 0 - # we are beginning from the end, so if data exists twice or more, only the last one should be replaced - get_position = static.RX_FRAME_BUFFER[search_position:].rfind(temp_burst_buffer) + # we are beginning from the end, so if data exists twice or more, + # only the last one should be replaced + get_position = static.RX_FRAME_BUFFER[search_position:].rfind( + temp_burst_buffer + ) # if we find data, replace it at this position with the new data and strip it if get_position >= 0: - static.RX_FRAME_BUFFER = static.RX_FRAME_BUFFER[:search_position + get_position] + static.RX_FRAME_BUFFER = static.RX_FRAME_BUFFER[ + : search_position + get_position + ] static.RX_FRAME_BUFFER += temp_burst_buffer - structlog.get_logger("structlog").warning("[TNC] ARQ | RX | replacing existing buffer data", - area=search_area, pos=get_position) + self.log.warning( + "[TNC] ARQ | RX | replacing existing buffer data", + area=search_area, + pos=get_position, + ) # if we dont find data n this range, we really have new data and going to replace it else: static.RX_FRAME_BUFFER += temp_burst_buffer - structlog.get_logger("structlog").debug("[TNC] ARQ | RX | appending data to buffer") + self.log.debug("[TNC] ARQ | RX | appending data to buffer") - # lets check if we didnt receive a BOF and EOF yet to avoid sending ack frames if we already received all data - if (not self.rx_frame_bof_received and - not self.rx_frame_eof_received and - data_in.find(self.data_frame_eof) < 0): + # lets check if we didnt receive a BOF and EOF yet to avoid sending + # ack frames if we already received all data + if ( + not self.rx_frame_bof_received + and not self.rx_frame_eof_received + and data_in.find(self.data_frame_eof) < 0 + ): self.frame_received_counter += 1 if self.frame_received_counter >= 2: @@ -525,29 +607,39 @@ class DATA: self.set_listening_modes(self.mode_list[self.speed_level]) # Create and send ACK frame - structlog.get_logger("structlog").info("[TNC] ARQ | RX | SENDING ACK") + self.log.info("[TNC] ARQ | RX | SENDING ACK") self.send_burst_ack_frame(snr) # Reset n retries per burst counter self.n_retries_per_burst = 0 # calculate statistics - self.calculate_transfer_rate_rx(self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER)) + self.calculate_transfer_rate_rx( + self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER) + ) elif RX_N_FRAME_OF_BURST == RX_N_FRAMES_PER_BURST - 1: # We have "Nones" in our rx buffer, # Check if we received last frame of burst - this is an indicator for missed frames. - # With this way of doing this, we always MUST receive the last frame of a burst otherwise the entire - # burst is lost - structlog.get_logger("structlog").debug("[TNC] all frames in burst received:", frame=RX_N_FRAME_OF_BURST, - frames=RX_N_FRAMES_PER_BURST) + # With this way of doing this, we always MUST receive the last + # frame of a burst otherwise the entire burst is lost + self.log.debug( + "[TNC] all frames in burst received:", + frame=RX_N_FRAME_OF_BURST, + frames=RX_N_FRAMES_PER_BURST, + ) self.send_retransmit_request_frame(freedv) - self.calculate_transfer_rate_rx(self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER)) + self.calculate_transfer_rate_rx( + self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER) + ) # Should never reach this point else: - structlog.get_logger("structlog").error("[TNC] data_handler: Should not reach this point...", - frame=RX_N_FRAME_OF_BURST, frames=RX_N_FRAMES_PER_BURST) + self.log.error( + "[TNC] data_handler: Should not reach this point...", + frame=RX_N_FRAME_OF_BURST, + frames=RX_N_FRAMES_PER_BURST, + ) # We have a BOF and EOF flag in our data. If we received both we received our frame. # In case of loosing data, but we received already a BOF and EOF we need to make sure, we @@ -557,27 +649,41 @@ class DATA: # get total bytes per transmission information as soon we recevied a frame with a BOF if bof_position >= 0: - payload = static.RX_FRAME_BUFFER[bof_position + len(self.data_frame_bof):eof_position] + payload = static.RX_FRAME_BUFFER[ + bof_position + len(self.data_frame_bof) : eof_position + ] frame_length = int.from_bytes(payload[4:8], "big") # 4:8 4bytes static.TOTAL_BYTES = frame_length compression_factor = int.from_bytes(payload[8:9], "big") # 4:8 4bytes - compression_factor = np.clip(compression_factor, 0, 255) # limit to max value of 255 + compression_factor = np.clip( + compression_factor, 0, 255 + ) # limit to max value of 255 static.ARQ_COMPRESSION_FACTOR = compression_factor / 10 - self.calculate_transfer_rate_rx(self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER)) + self.calculate_transfer_rate_rx( + self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER) + ) - if bof_position >= 0 and eof_position > 0 and None not in static.RX_BURST_BUFFER: - structlog.get_logger("structlog").debug("[TNC] arq_data_received:", bof_position=bof_position, - eof_position=eof_position) - # print(f"bof_position {bof_position} / eof_position {eof_position}") + if ( + bof_position >= 0 + and eof_position > 0 + and None not in static.RX_BURST_BUFFER + ): + self.log.debug( + "[TNC] arq_data_received:", + bof_position=bof_position, + eof_position=eof_position, + ) self.rx_frame_bof_received = True self.rx_frame_eof_received = True # Extract raw data from buffer - payload = static.RX_FRAME_BUFFER[bof_position + len(self.data_frame_bof):eof_position] + payload = static.RX_FRAME_BUFFER[ + bof_position + len(self.data_frame_bof) : eof_position + ] # Get the data frame crc - data_frame_crc = payload[:4] # 0:4 4bytes + data_frame_crc = payload[:4] # 0:4 = 4 bytes # Get the data frame length - frame_length = int.from_bytes(payload[4:8], "big") # 4:8 4bytes + frame_length = int.from_bytes(payload[4:8], "big") # 4:8 = 4 bytes static.TOTAL_BYTES = frame_length # 8:9 = compression factor @@ -586,83 +692,105 @@ class DATA: # Check if data_frame_crc is equal with received crc if data_frame_crc == data_frame_crc_received: - structlog.get_logger("structlog").info("[TNC] ARQ | RX | DATA FRAME SUCESSFULLY RECEIVED") + self.log.info("[TNC] ARQ | RX | DATA FRAME SUCESSFULLY RECEIVED") # Decompress the data frame data_frame_decompressed = zlib.decompress(data_frame) - static.ARQ_COMPRESSION_FACTOR = len(data_frame_decompressed) / len(data_frame) + static.ARQ_COMPRESSION_FACTOR = len(data_frame_decompressed) / len( + data_frame + ) data_frame = data_frame_decompressed uniqueid = str(uuid.uuid4()) timestamp = int(time.time()) # check if callsign ssid override - valid, mycallsign = helpers.check_callsign(self.mycallsign, self.received_mycall_crc) - if not valid: + _valid, mycallsign = helpers.check_callsign( + self.mycallsign, self.received_mycall_crc + ) + if not _valid: # ARQ data packet not for me. - if not TESTMODE: - self.arq_cleanup() + self.arq_cleanup() return # Re-code data_frame in base64, UTF-8 for JSON UI communication. - base64_data = base64.b64encode(data_frame).decode("utf-8") - static.RX_BUFFER.append([uniqueid, timestamp, static.DXCALLSIGN, static.DXGRID, base64_data]) - jsondata = {"arq": "received", "uuid": uniqueid, "timestamp": timestamp, - "mycallsign": str(mycallsign, 'utf-8'), "dxcallsign": str(static.DXCALLSIGN, 'utf-8'), - "dxgrid": str(static.DXGRID, 'utf-8'), "data": base64_data} + base64_data = base64.b64encode(data_frame).decode("UTF-8") + static.RX_BUFFER.append( + [uniqueid, timestamp, static.DXCALLSIGN, static.DXGRID, base64_data] + ) + jsondata = { + "arq": "received", + "uuid": uniqueid, + "timestamp": timestamp, + "mycallsign": str(mycallsign, "UTF-8"), + "dxcallsign": str(static.DXCALLSIGN, "UTF-8"), + "dxgrid": str(static.DXGRID, "UTF-8"), + "data": base64_data, + } json_data_out = json.dumps(jsondata) - structlog.get_logger("structlog").debug("[TNC] arq_data_received:", jsondata=jsondata) - # print(jsondata) + self.log.debug("[TNC] arq_data_received:", jsondata=jsondata) sock.SOCKET_QUEUE.put(json_data_out) static.INFO.append("ARQ;RECEIVING;SUCCESS") - structlog.get_logger("structlog").info("[TNC] ARQ | RX | SENDING DATA FRAME ACK", snr=snr, - crc=data_frame_crc.hex()) + self.log.info( + "[TNC] ARQ | RX | SENDING DATA FRAME ACK", + snr=snr, + crc=data_frame_crc.hex(), + ) self.send_data_ack_frame(snr) - # update our statistics AFTER the frame ACK - self.calculate_transfer_rate_rx(self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER)) + # Update statistics AFTER the frame ACK is sent + self.calculate_transfer_rate_rx( + self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER) + ) - structlog.get_logger("structlog").info("[TNC] | RX | DATACHANNEL [" + - str(self.mycallsign, 'utf-8') + "]<< >>[" + str( - static.DXCALLSIGN, 'utf-8') + "]", snr=snr) + self.log.info( + "[TNC] | RX | DATACHANNEL [" + + str(self.mycallsign, "UTF-8") + + "]<< >>[" + + str(static.DXCALLSIGN, "UTF-8") + + "]", + snr=snr, + ) else: static.INFO.append("ARQ;RECEIVING;FAILED") - structlog.get_logger("structlog").warning("[TNC] ARQ | RX | DATA FRAME NOT SUCESSFULLY RECEIVED!", - e="wrong crc", expected=data_frame_crc, - received=data_frame_crc_received, - overflows=static.BUFFER_OVERFLOW_COUNTER) + self.log.warning( + "[TNC] ARQ | RX | DATA FRAME NOT SUCESSFULLY RECEIVED!", + e="wrong crc", + expected=data_frame_crc, + received=data_frame_crc_received, + overflows=static.BUFFER_OVERFLOW_COUNTER, + ) - structlog.get_logger("structlog").info("[TNC] ARQ | RX | Sending NACK") + self.log.info("[TNC] ARQ | RX | Sending NACK") self.send_burst_nack_frame(snr) - # update session timeout - self.arq_session_last_received = int(time.time()) # we need to update our timeout timestamp + # Update timeout timestamp + self.arq_session_last_received = int(time.time()) - # And finally we do a cleanup of our buffers and states - # do cleanup only when not in testmode - if not TESTMODE: - self.arq_cleanup() + # Finally cleanup our buffers and states, + self.arq_cleanup() def arq_transmit(self, data_out: bytes, mode: int, n_frames_per_burst: int): """ + Transmit ARQ frame Args: data_out:bytes: mode:int: n_frames_per_burst:int: - Returns: - """ self.arq_file_transfer = True - self.speed_level = len(self.mode_list) - 1 # speed level for selecting mode + # Start at the highest speed level for selected speed mode + self.speed_level = len(self.mode_list) - 1 static.ARQ_SPEED_LEVEL = self.speed_level TX_N_SENT_BYTES = 0 # already sent bytes per data frame self.tx_n_retry_of_burst = 0 # retries we already sent data - TX_N_MAX_RETRIES_PER_BURST = 50 # max amount of retries we sent before frame is lost + # Maximum number of retries to send before declaring a frame is lost + TX_N_MAX_RETRIES_PER_BURST = 50 TX_N_FRAMES_PER_BURST = n_frames_per_burst # amount of n frames per burst TX_BUFFER = [] # our buffer for appending new data @@ -674,15 +802,20 @@ class DATA: # save len of data_out to TOTAL_BYTES for our statistics --> kBytes # static.TOTAL_BYTES = round(len(data_out) / 1024, 2) static.TOTAL_BYTES = len(data_out) - frame_total_size = len(data_out).to_bytes(4, byteorder='big') + frame_total_size = len(data_out).to_bytes(4, byteorder="big") static.INFO.append("ARQ;TRANSMITTING") - jsondata = {"arq": "transmission", "status": "transmitting", "uuid": self.transmission_uuid, - "percent": static.ARQ_TRANSMISSION_PERCENT, "bytesperminute": static.ARQ_BYTES_PER_MINUTE} + jsondata = { + "arq": "transmission", + "status": "transmitting", + "uuid": self.transmission_uuid, + "percent": static.ARQ_TRANSMISSION_PERCENT, + "bytesperminute": static.ARQ_BYTES_PER_MINUTE, + } json_data_out = json.dumps(jsondata) sock.SOCKET_QUEUE.put(json_data_out) - structlog.get_logger("structlog").info("[TNC] | TX | DATACHANNEL", mode=mode, Bytes=static.TOTAL_BYTES) + self.log.info("[TNC] | TX | DATACHANNEL", mode=mode, Bytes=static.TOTAL_BYTES) # Compress data frame data_frame_compressed = zlib.compress(data_out) @@ -698,16 +831,27 @@ class DATA: # Append a crc and the begin and end of file indicators frame_payload_crc = helpers.get_crc_32(data_out) - structlog.get_logger("structlog").debug("[TNC] frame payload CRC:", crc=frame_payload_crc) + self.log.debug("[TNC] frame payload CRC:", crc=frame_payload_crc) - # data_out = self.data_frame_bof + frame_payload_crc + data_out + self.data_frame_eof - data_out = self.data_frame_bof + frame_payload_crc + frame_total_size + compression_factor + data_out + self.data_frame_eof + # Assemble the data frame + data_out = ( + self.data_frame_bof + + frame_payload_crc + + frame_total_size + + compression_factor + + data_out + + self.data_frame_eof + ) - # initial bufferposition is 0 + # Initial bufferposition is 0 bufferposition = bufferposition_end = 0 - # iterate through data out buffer - while bufferposition < len(data_out) and not self.data_frame_ack_received and static.ARQ_STATE: + # Iterate through data_out buffer + while ( + bufferposition < len(data_out) + and not self.data_frame_ack_received + and static.ARQ_STATE + ): # we have TX_N_MAX_RETRIES_PER_BURST attempts for sending a burst for self.tx_n_retry_of_burst in range(TX_N_MAX_RETRIES_PER_BURST): @@ -716,43 +860,42 @@ class DATA: # force usage of selected mode if mode != 255: data_mode = mode - structlog.get_logger("structlog").debug("[TNC] FIXED MODE:", mode=data_mode) + self.log.debug("[TNC] FIXED MODE:", mode=data_mode) else: # we are doing a modulo check of transmission retries of the actual burst # every 2nd retry which failes, decreases speedlevel by 1. # as soon as we received an ACK for the current burst, speed_level will increase # by 1. - # the can be optimised by checking the optimal speed level for the current conditions - ''' - if not self.tx_n_retry_of_burst % 2 and self.tx_n_retry_of_burst > 0: - self.speed_level -= 1 - if self.speed_level < 0: - self.speed_level = 0 - ''' + # The intent is to optimize speed by adapting to the current RF conditions. + # if not self.tx_n_retry_of_burst % 2 and self.tx_n_retry_of_burst > 0: + # self.speed_level = max(self.speed_level - 1, 0) # if self.tx_n_retry_of_burst <= 1: # self.speed_level += 1 - # if self.speed_level >= len(self.mode_list)-1: - # self.speed_level = len(self.mode_list)-1 + # self.speed_level = max(self.speed_level + 1, len(self.mode_list) - 1) - # if speed level is greater than our available modes, set speed level to maximum = lenght of mode list -1 - - # if speed level is greater than our available modes, set speed level to maximum = lenght of mode list -1 - if self.speed_level >= len(self.mode_list): - self.speed_level = len(self.mode_list) - 1 - static.ARQ_SPEED_LEVEL = self.speed_level + # Bound speed level to: + # - minimum of either the speed or the length of mode list - 1 + # - maximum of either the speed or zero + self.speed_level = min(self.speed_level, len(self.mode_list) - 1) + self.speed_level = max(self.speed_level, 0) + static.ARQ_SPEED_LEVEL = self.speed_level data_mode = self.mode_list[self.speed_level] - structlog.get_logger("structlog").debug("[TNC] Speed-level:", level=self.speed_level, - retry=self.tx_n_retry_of_burst, mode=data_mode) + self.log.debug( + "[TNC] Speed-level:", + level=self.speed_level, + retry=self.tx_n_retry_of_burst, + mode=data_mode, + ) - # payload information + # Payload information payload_per_frame = modem.get_bytes_per_frame(data_mode) - 2 - # tempbuffer list for storing our data frames + # Tempbuffer list for storing our data frames tempbuffer = [] - # append data frames with TX_N_FRAMES_PER_BURST to tempbuffer + # Append data frames with TX_N_FRAMES_PER_BURST to tempbuffer # TODO: this part needs a complete rewrite! # TX_N_FRAMES_PER_BURST = 1 is working @@ -762,52 +905,59 @@ class DATA: arqheader[2:5] = static.DXCALLSIGN_CRC arqheader[5:8] = static.MYCALLSIGN_CRC - bufferposition_end = (bufferposition + payload_per_frame - len(arqheader)) + bufferposition_end = bufferposition + payload_per_frame - len(arqheader) - # normal behavior + # Normal condition if bufferposition_end <= len(data_out): frame = data_out[bufferposition:bufferposition_end] frame = arqheader + frame - # this point shouldnt reached that often + # This point shouldn't reached that often elif bufferposition > len(data_out): break - # the last bytes of a frame + # Pad the last bytes of a frame else: extended_data_out = data_out[bufferposition:] - extended_data_out += bytes([0]) * (payload_per_frame - len(extended_data_out) - len(arqheader)) + extended_data_out += bytes([0]) * ( + payload_per_frame - len(extended_data_out) - len(arqheader) + ) frame = arqheader + extended_data_out - # append frame to tempbuffer for transmission + # Append frame to tempbuffer for transmission tempbuffer.append(frame) - structlog.get_logger("structlog").debug("[TNC] tempbuffer:", tempbuffer=tempbuffer) - structlog.get_logger("structlog").info("[TNC] ARQ | TX | FRAMES", mode=data_mode, - fpb=TX_N_FRAMES_PER_BURST, retry=self.tx_n_retry_of_burst) + self.log.debug("[TNC] tempbuffer:", tempbuffer=tempbuffer) + self.log.info( + "[TNC] ARQ | TX | FRAMES", + mode=data_mode, + fpb=TX_N_FRAMES_PER_BURST, + retry=self.tx_n_retry_of_burst, + ) - # we need to set our TRANSMITTING flag before we are adding an object the transmit queue - # this is not that nice, we could improve this somehow - static.TRANSMITTING = True - modem.MODEM_TRANSMIT_QUEUE.put([data_mode, 1, 0, tempbuffer]) + self.enqueue_frame_for_tx(bytearray(tempbuffer), c2_mode=data_mode) # wait while transmitting while static.TRANSMITTING: time.sleep(0.01) - # after transmission finished wait for an ACK or RPT frame - ''' - burstacktimeout = time.time() + BURST_ACK_TIMEOUT_SECONDS + 100 - while not self.burst_ack and not self.burst_nack and not self.rpt_request_received and not self.data_frame_ack_received and time.time() < burstacktimeout and static.ARQ_STATE: - time.sleep(0.01) - ''' + # After transmission finished, wait for an ACK or RPT frame # burstacktimeout = time.time() + BURST_ACK_TIMEOUT_SECONDS + 100 - while (static.ARQ_STATE and not - (self.burst_ack or self.burst_nack or - self.rpt_request_received or self.data_frame_ack_received)): + # while (not self.burst_ack and not self.burst_nack and + # not self.rpt_request_received and not self.data_frame_ack_received and + # time.time() < burstacktimeout and static.ARQ_STATE): + # time.sleep(0.01) + + # burstacktimeout = time.time() + BURST_ACK_TIMEOUT_SECONDS + 100 + while static.ARQ_STATE and not ( + self.burst_ack + or self.burst_nack + or self.rpt_request_received + or self.data_frame_ack_received + ): time.sleep(0.01) - # once we received a burst ack, reset its state and break the RETRIES loop + # Once we received a burst ack, reset its state and break the RETRIES loop if self.burst_ack: self.burst_ack = False # reset ack state self.tx_n_retry_of_burst = 0 # reset retries @@ -823,26 +973,39 @@ class DATA: if self.data_frame_ack_received: break # break retry loop - # we need this part for leaving the repeat loop + # We need this part for leaving the repeat loop # static.ARQ_STATE == 'DATA' --> when stopping transmission manually if not static.ARQ_STATE: # print("not ready for data...leaving loop....") break - self.calculate_transfer_rate_tx(tx_start_of_transmission, bufferposition_end, len(data_out)) + self.calculate_transfer_rate_tx( + tx_start_of_transmission, bufferposition_end, len(data_out) + ) # NEXT ATTEMPT - structlog.get_logger("structlog").debug("[TNC] ATTEMPT:", retry=self.tx_n_retry_of_burst, - maxretries=TX_N_MAX_RETRIES_PER_BURST, - overflows=static.BUFFER_OVERFLOW_COUNTER) + self.log.debug( + "[TNC] ATTEMPT:", + retry=self.tx_n_retry_of_burst, + maxretries=TX_N_MAX_RETRIES_PER_BURST, + overflows=static.BUFFER_OVERFLOW_COUNTER, + ) + # End of FOR loop # update buffer position bufferposition = bufferposition_end # update stats - self.calculate_transfer_rate_tx(tx_start_of_transmission, bufferposition_end, len(data_out)) + self.calculate_transfer_rate_tx( + tx_start_of_transmission, bufferposition_end, len(data_out) + ) - jsondata = {"arq": "transmission", "status": "transmitting", "uuid": self.transmission_uuid, - "percent": static.ARQ_TRANSMISSION_PERCENT, "bytesperminute": static.ARQ_BYTES_PER_MINUTE} + jsondata = { + "arq": "transmission", + "status": "transmitting", + "uuid": self.transmission_uuid, + "percent": static.ARQ_TRANSMISSION_PERCENT, + "bytesperminute": static.ARQ_BYTES_PER_MINUTE, + } json_data_out = json.dumps(jsondata) sock.SOCKET_QUEUE.put(json_data_out) @@ -850,38 +1013,52 @@ class DATA: if self.data_frame_ack_received: static.INFO.append("ARQ;TRANSMITTING;SUCCESS") - jsondata = {"arq": "transmission", "status": "success", "uuid": self.transmission_uuid, - "percent": static.ARQ_TRANSMISSION_PERCENT, "bytesperminute": static.ARQ_BYTES_PER_MINUTE} + jsondata = { + "arq": "transmission", + "status": "success", + "uuid": self.transmission_uuid, + "percent": static.ARQ_TRANSMISSION_PERCENT, + "bytesperminute": static.ARQ_BYTES_PER_MINUTE, + } json_data_out = json.dumps(jsondata) sock.SOCKET_QUEUE.put(json_data_out) - structlog.get_logger("structlog").info("[TNC] ARQ | TX | DATA TRANSMITTED!", - BytesPerMinute=static.ARQ_BYTES_PER_MINUTE, - BitsPerSecond=static.ARQ_BITS_PER_SECOND, - overflows=static.BUFFER_OVERFLOW_COUNTER) + self.log.info( + "[TNC] ARQ | TX | DATA TRANSMITTED!", + BytesPerMinute=static.ARQ_BYTES_PER_MINUTE, + BitsPerSecond=static.ARQ_BITS_PER_SECOND, + overflows=static.BUFFER_OVERFLOW_COUNTER, + ) else: static.INFO.append("ARQ;TRANSMITTING;FAILED") - jsondata = {"arq": "transmission", "status": "failed", "uuid": self.transmission_uuid, - "percent": static.ARQ_TRANSMISSION_PERCENT, "bytesperminute": static.ARQ_BYTES_PER_MINUTE} + jsondata = { + "arq": "transmission", + "status": "failed", + "uuid": self.transmission_uuid, + "percent": static.ARQ_TRANSMISSION_PERCENT, + "bytesperminute": static.ARQ_BYTES_PER_MINUTE, + } json_data_out = json.dumps(jsondata) sock.SOCKET_QUEUE.put(json_data_out) - structlog.get_logger("structlog").info("[TNC] ARQ | TX | TRANSMISSION FAILED OR TIME OUT!", - overflows=static.BUFFER_OVERFLOW_COUNTER) + self.log.info( + "[TNC] ARQ | TX | TRANSMISSION FAILED OR TIME OUT!", + overflows=static.BUFFER_OVERFLOW_COUNTER, + ) self.stop_transmission() - # and last but not least doing a state cleanup - # do cleanup only when not in testmode - if not TESTMODE: - self.arq_cleanup() - else: - # quit after transmission + # Last but not least do a state cleanup + self.arq_cleanup() + if TESTMODE: + # Quit after transmission sys.exit(0) # signalling frames received def burst_ack_received(self, data_in: bytes): """ + Received a NACK for a transmitted frame, keep track and + make adjustments to speed level if needed. Args: data_in:bytes: @@ -889,154 +1066,202 @@ class DATA: Returns: """ - # increase speed level if we received a burst ack - # self.speed_level += 1 - # if self.speed_level >= len(self.mode_list)-1: - # self.speed_level = len(self.mode_list)-1 + # Increase speed level if we received a burst ack + # self.speed_level = min(self.speed_level + 1, len(self.mode_list) - 1) - # only process data if we are in ARQ and BUSY state + # Process data only if we are in ARQ and BUSY state if static.ARQ_STATE: - helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR, - static.FREQ_OFFSET, static.HAMLIB_FREQUENCY) - self.burst_ack = True # Force data loops of TNC to stop and continue with next frame - self.data_channel_last_received = int(time.time()) # we need to update our timeout timestamp + helpers.add_to_heard_stations( + static.DXCALLSIGN, + static.DXGRID, + "DATA-CHANNEL", + static.SNR, + static.FREQ_OFFSET, + static.HAMLIB_FREQUENCY, + ) + # Force data loops of TNC to stop and continue with next frame + self.burst_ack = True + # Update our timeout timestamp + self.data_channel_last_received = int(time.time()) self.burst_ack_snr = int.from_bytes(bytes(data_in[7:8]), "big") self.speed_level = int.from_bytes(bytes(data_in[8:9]), "big") static.ARQ_SPEED_LEVEL = self.speed_level - structlog.get_logger("structlog").debug("[TNC] burst_ack_received:", speed_level=self.speed_level) - # print(self.speed_level) - # reset burst nack counter + self.log.debug("[TNC] burst_ack_received:", speed_level=self.speed_level) + + # Reset burst nack counter self.burst_nack_counter = 0 - # reset n retries per burst counter + # Reset n retries per burst counter self.n_retries_per_burst = 0 # signalling frames received def burst_nack_received(self, data_in: bytes): """ + Received a NACK for a transmitted frame, keep track and + make adjustments to speed level if needed. Args: data_in:bytes: - Returns: - """ - # increase speed level if we received a burst ack - # self.speed_level += 1 - # if self.speed_level >= len(self.mode_list)-1: - # self.speed_level = len(self.mode_list)-1 + # Decrease speed level if we received a burst nack + # self.speed_level = max(self.speed_level - 1, 0) # only process data if we are in ARQ and BUSY state if static.ARQ_STATE: - helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR, - static.FREQ_OFFSET, static.HAMLIB_FREQUENCY) - self.burst_nack = True # Force data loops of TNC to stop and continue with next frame - self.data_channel_last_received = int(time.time()) # we need to update our timeout timestamp + helpers.add_to_heard_stations( + static.DXCALLSIGN, + static.DXGRID, + "DATA-CHANNEL", + static.SNR, + static.FREQ_OFFSET, + static.HAMLIB_FREQUENCY, + ) + self.burst_nack = ( + True # Force data loops of TNC to stop and continue with next frame + ) + self.data_channel_last_received = int( + time.time() + ) # we need to update our timeout timestamp self.burst_ack_snr = int.from_bytes(bytes(data_in[7:8]), "big") self.speed_level = int.from_bytes(bytes(data_in[8:9]), "big") static.ARQ_SPEED_LEVEL = self.speed_level self.burst_nack_counter += 1 - structlog.get_logger("structlog").debug("[TNC] burst_nack_received:", speed_level=self.speed_level) - # print(self.speed_level) + self.log.debug("[TNC] burst_nack_received:", speed_level=self.speed_level) def frame_ack_received(self): - """ """ - # only process data if we are in ARQ and BUSY state + """Received an ACK for a transmitted frame""" + # Process data only if we are in ARQ and BUSY state if static.ARQ_STATE: - helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR, - static.FREQ_OFFSET, static.HAMLIB_FREQUENCY) - self.data_frame_ack_received = True # Force data loops of TNC to stop and continue with next frame - self.data_channel_last_received = int(time.time()) # we need to update our timeout timestamp - self.arq_session_last_received = int(time.time()) # we need to update our timeout timestamp + helpers.add_to_heard_stations( + static.DXCALLSIGN, + static.DXGRID, + "DATA-CHANNEL", + static.SNR, + static.FREQ_OFFSET, + static.HAMLIB_FREQUENCY, + ) + self.data_frame_ack_received = ( + True # Force data loops of TNC to stop and continue with next frame + ) + self.data_channel_last_received = int( + time.time() + ) # we need to update our timeout timestamp + self.arq_session_last_received = int( + time.time() + ) # we need to update our timeout timestamp def frame_nack_received(self, data_in: bytes): # pylint: disable=unused-argument """ + Received a NACK for a transmitted framt Args: data_in:bytes: - Returns: - """ - helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY) + helpers.add_to_heard_stations( + static.DXCALLSIGN, + static.DXGRID, + "DATA-CHANNEL", + static.SNR, + static.FREQ_OFFSET, + static.HAMLIB_FREQUENCY, + ) static.INFO.append("ARQ;TRANSMITTING;FAILED") - jsondata = {"arq": "transmission", "status": "failed", "uuid": self.transmission_uuid, - "percent": static.ARQ_TRANSMISSION_PERCENT, "bytesperminute": static.ARQ_BYTES_PER_MINUTE} + jsondata = { + "arq": "transmission", + "status": "failed", + "uuid": self.transmission_uuid, + "percent": static.ARQ_TRANSMISSION_PERCENT, + "bytesperminute": static.ARQ_BYTES_PER_MINUTE, + } json_data_out = json.dumps(jsondata) sock.SOCKET_QUEUE.put(json_data_out) - self.arq_session_last_received = int(time.time()) # we need to update our timeout timestamp + # Update the timeout timestamp + self.arq_session_last_received = int(time.time()) - if not TESTMODE: - self.arq_cleanup() + self.arq_cleanup() def burst_rpt_received(self, data_in: bytes): """ + Repeat request frame received for transmitted frame Args: data_in:bytes: - Returns: - """ - # only process data if we are in ARQ and BUSY state - if static.ARQ_STATE and static.TNC_STATE == 'BUSY': - helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR, - static.FREQ_OFFSET, static.HAMLIB_FREQUENCY) + # Only process data if we are in ARQ and BUSY state + if static.ARQ_STATE and static.TNC_STATE == "BUSY": + helpers.add_to_heard_stations( + static.DXCALLSIGN, + static.DXGRID, + "DATA-CHANNEL", + static.SNR, + static.FREQ_OFFSET, + static.HAMLIB_FREQUENCY, + ) self.rpt_request_received = True - self.data_channel_last_received = int(time.time()) # we need to update our timeout timestamp + self.data_channel_last_received = int( + time.time() + ) # we need to update our timeout timestamp self.rpt_request_buffer = [] missing_area = bytes(data_in[3:12]) # 1:9 for i in range(0, 6, 2): - if not missing_area[i:i + 2].endswith(b'\x00\x00'): - missing = missing_area[i:i + 2] + if not missing_area[i : i + 2].endswith(b"\x00\x00"): + missing = missing_area[i : i + 2] self.rpt_request_buffer.insert(0, missing) # ############################################################################################################ # ARQ SESSION HANDLER # ############################################################################################################ - def arq_session_handler(self, callsign): + def arq_session_handler(self) -> bool: """ + Create a session with `callsign` and wait until the session is open. Args: callsign: Returns: - + True if the session was opened successfully + False if the session open request failed """ # das hier müssen wir checken. Sollte vielleicht in INIT!!! self.datachannel_timeout = False - structlog.get_logger("structlog").info( - "[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]>> <<[" + str(static.DXCALLSIGN, 'utf-8') + "]", - state=static.ARQ_SESSION_STATE) + self.log.info( + "[TNC] SESSION [" + + str(self.mycallsign, "UTF-8") + + "]>> <<[" + + str(static.DXCALLSIGN, "UTF-8") + + "]", + state=static.ARQ_SESSION_STATE, + ) - self.open_session(callsign) + self.open_session() # wait until data channel is open while not static.ARQ_SESSION and not self.arq_session_timeout: time.sleep(0.01) - static.ARQ_SESSION_STATE = 'connecting' + static.ARQ_SESSION_STATE = "connecting" - if static.ARQ_SESSION and static.ARQ_SESSION_STATE == 'connected': + if static.ARQ_SESSION and static.ARQ_SESSION_STATE == "connected": # static.ARQ_SESSION_STATE = 'connected' return True - static.ARQ_SESSION_STATE = 'failed' + static.ARQ_SESSION_STATE = "failed" return False - def open_session(self, callsign): + def open_session(self) -> bool: """ - - Args: - callsign: + Create and send the frame to request a connection. Returns: - + True if the session was opened successfully + False if the session open request failed """ self.IS_ARQ_SESSION_MASTER = True - static.ARQ_SESSION_STATE = 'connecting' + static.ARQ_SESSION_STATE = "connecting" connection_frame = bytearray(14) connection_frame[:1] = bytes([221]) @@ -1047,22 +1272,27 @@ class DATA: while not static.ARQ_SESSION: time.sleep(0.01) for attempt in range(1, self.session_connect_max_retries + 1): - structlog.get_logger("structlog").info( - "[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]>>?<<[" + str(static.DXCALLSIGN, - 'utf-8') + "]", a=attempt, - state=static.ARQ_SESSION_STATE) + self.log.info( + "[TNC] SESSION [" + + str(self.mycallsign, "UTF-8") + + "]>>?<<[" + + str(static.DXCALLSIGN, "UTF-8") + + "]", + a=attempt, + state=static.ARQ_SESSION_STATE, + ) self.enqueue_frame_for_tx(connection_frame) + # Wait for a time, looking to see if `static.ARQ_SESSION` + # indicates we've received a positive response from the far station. timeout = time.time() + 3 while time.time() < timeout: time.sleep(0.01) - # break if data channel is opened + # Stop waiting if data channel is opened if static.ARQ_SESSION: # eventuell einfach nur return true um die nächste break ebene zu vermeiden? return True - # if static.ARQ_SESSION: - # break # Session connect timeout, send close_session frame to # attempt to cleanup the far-side, if it received the @@ -1071,46 +1301,69 @@ class DATA: self.close_session() return False - def received_session_opener(self, data_in: bytes): + # Given the while condition, it will only exit when `static.ARQ_SESSION` is True + return True + + def received_session_opener(self, data_in: bytes) -> None: """ + Received a session open request packet. Args: data_in:bytes: - - Returns: - """ self.IS_ARQ_SESSION_MASTER = False - static.ARQ_SESSION_STATE = 'connecting' + static.ARQ_SESSION_STATE = "connecting" + # Update the timeout timestamp self.arq_session_last_received = int(time.time()) static.DXCALLSIGN_CRC = bytes(data_in[4:7]) static.DXCALLSIGN = helpers.bytes_to_callsign(bytes(data_in[7:13])) - helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY) - structlog.get_logger("structlog").info( - "[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]>>|<<[" + str(static.DXCALLSIGN, 'utf-8') + "]", - state=static.ARQ_SESSION_STATE) + helpers.add_to_heard_stations( + static.DXCALLSIGN, + static.DXGRID, + "DATA-CHANNEL", + static.SNR, + static.FREQ_OFFSET, + static.HAMLIB_FREQUENCY, + ) + self.log.info( + "[TNC] SESSION [" + + str(self.mycallsign, "UTF-8") + + "]>>|<<[" + + str(static.DXCALLSIGN, "UTF-8") + + "]", + state=static.ARQ_SESSION_STATE, + ) static.ARQ_SESSION = True - static.TNC_STATE = 'BUSY' + static.TNC_STATE = "BUSY" self.transmit_session_heartbeat() - def close_session(self): - """ Close the ARQ session """ - static.ARQ_SESSION_STATE = 'disconnecting' - helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY) - structlog.get_logger("structlog").info( - "[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]<>[" + str(static.DXCALLSIGN, 'utf-8') + "]", - state=static.ARQ_SESSION_STATE) + def close_session(self) -> None: + """Close the ARQ session""" + static.ARQ_SESSION_STATE = "disconnecting" + helpers.add_to_heard_stations( + static.DXCALLSIGN, + static.DXGRID, + "DATA-CHANNEL", + static.SNR, + static.FREQ_OFFSET, + static.HAMLIB_FREQUENCY, + ) + self.log.info( + "[TNC] SESSION [" + + str(self.mycallsign, "UTF-8") + + "]<>[" + + str(static.DXCALLSIGN, "UTF-8") + + "]", + state=static.ARQ_SESSION_STATE, + ) static.INFO.append("ARQ;SESSION;CLOSE") self.IS_ARQ_SESSION_MASTER = False static.ARQ_SESSION = False - if not TESTMODE: - self.arq_cleanup() + self.arq_cleanup() self.send_disconnect_frame() @@ -1122,25 +1375,35 @@ class DATA: Args: data_in:bytes: - Returns: """ # Close the session if the DXCALLSIGN_CRC matches the station in static. _valid_crc, _ = helpers.check_callsign(static.DXCALLSIGN, bytes(data_in[4:7])) if _valid_crc: - static.ARQ_SESSION_STATE = 'disconnected' - helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR, - static.FREQ_OFFSET, static.HAMLIB_FREQUENCY) - structlog.get_logger("structlog").info( - "[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]<>[" + str(static.DXCALLSIGN, 'utf-8') + "]", - state=static.ARQ_SESSION_STATE) + static.ARQ_SESSION_STATE = "disconnected" + helpers.add_to_heard_stations( + static.DXCALLSIGN, + static.DXGRID, + "DATA-CHANNEL", + static.SNR, + static.FREQ_OFFSET, + static.HAMLIB_FREQUENCY, + ) + self.log.info( + "[TNC] SESSION [" + + str(self.mycallsign, "UTF-8") + + "]<>[" + + str(static.DXCALLSIGN, "UTF-8") + + "]", + state=static.ARQ_SESSION_STATE, + ) static.INFO.append("ARQ;SESSION;CLOSE") self.IS_ARQ_SESSION_MASTER = False static.ARQ_SESSION = False self.arq_cleanup() - def transmit_session_heartbeat(self): - """ """ + def transmit_session_heartbeat(self) -> None: + """Send ARQ sesion heartbeat while connected""" # static.ARQ_SESSION = True # static.TNC_STATE = 'BUSY' # static.ARQ_SESSION_STATE = 'connected' @@ -1152,37 +1415,50 @@ class DATA: self.enqueue_frame_for_tx(connection_frame) - def received_session_heartbeat(self, data_in: bytes): + def received_session_heartbeat(self, data_in: bytes) -> None: """ - + Received an ARQ session heartbeat, record and update state accordingly. Args: data_in:bytes: - Returns: - """ # Accept session data if the DXCALLSIGN_CRC matches the station in static. _valid_crc, _ = helpers.check_callsign(static.DXCALLSIGN, bytes(data_in[4:7])) if _valid_crc: - structlog.get_logger("structlog").debug("[TNC] Received session heartbeat") - helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'SESSION-HB', static.SNR, - static.FREQ_OFFSET, static.HAMLIB_FREQUENCY) - - self.arq_session_last_received = int(time.time()) # we need to update our timeout timestamp + self.log.debug("[TNC] Received session heartbeat") + helpers.add_to_heard_stations( + static.DXCALLSIGN, + static.DXGRID, + "SESSION-HB", + static.SNR, + static.FREQ_OFFSET, + static.HAMLIB_FREQUENCY, + ) static.ARQ_SESSION = True - static.ARQ_SESSION_STATE = 'connected' - static.TNC_STATE = 'BUSY' + static.ARQ_SESSION_STATE = "connected" + static.TNC_STATE = "BUSY" + + # Update the timeout timestamps + self.arq_session_last_received = int(time.time()) self.data_channel_last_received = int(time.time()) + if not self.IS_ARQ_SESSION_MASTER and not self.arq_file_transfer: self.transmit_session_heartbeat() - # ############################################################################################################ + ########################################################################################################## # ARQ DATA CHANNEL HANDLER - # ############################################################################################################ - def open_dc_and_transmit(self, data_out: bytes, mode: int, n_frames_per_burst: int, transmission_uuid: str, - mycallsign): + ########################################################################################################## + def open_dc_and_transmit( + self, + data_out: bytes, + mode: int, + n_frames_per_burst: int, + transmission_uuid: str, + mycallsign, + ) -> bool: """ + Open data channel and transmit data Args: data_out:bytes: @@ -1190,12 +1466,13 @@ class DATA: n_frames_per_burst:int: Returns: - + True if the data session was opened and the data was sent + False if the data session was not opened """ # overwrite mycallsign in case of different SSID self.mycallsign = mycallsign - static.TNC_STATE = 'BUSY' + static.TNC_STATE = "BUSY" self.arq_file_transfer = True self.transmission_uuid = transmission_uuid @@ -1219,32 +1496,39 @@ class DATA: if static.ARQ_STATE: self.arq_transmit(data_out, mode, n_frames_per_burst) - else: - return False + return True - def arq_open_data_channel(self, mode: int, n_frames_per_burst: int, mycallsign): + return False + + def arq_open_data_channel( + self, mode: int, n_frames_per_burst: int, mycallsign + ) -> bool: """ + Open an ARQ data channel. Args: mode:int: n_frames_per_burst:int: Returns: - + True if the data channel was opened successfully + False if the data channel failed to open """ self.is_IRS = False + + # Update the timeout timestamp self.data_channel_last_received = int(time.time()) if static.LOW_BANDWITH_MODE and mode == 255: frametype = bytes([227]) - structlog.get_logger("structlog").debug("[TNC] Requesting low bandwidth mode") + self.log.debug("[TNC] Requesting low bandwidth mode") else: frametype = bytes([225]) - structlog.get_logger("structlog").debug("[TNC] Requesting high bandwidth mode") + self.log.debug("[TNC] Requesting high bandwidth mode") if 230 <= mode <= 240: - structlog.get_logger("structlog").debug("[TNC] Requesting manual mode --> not yet implemented ") + self.log.debug("[TNC] Requesting manual mode --> not yet implemented ") frametype = bytes([mode]) connection_frame = bytearray(14) @@ -1256,57 +1540,67 @@ class DATA: while not static.ARQ_STATE: time.sleep(0.01) - for attempt in range(1, self.data_channel_max_retries + 1): + for attempt in range(self.data_channel_max_retries): static.INFO.append("DATACHANNEL;OPENING") - structlog.get_logger("structlog").info( - "[TNC] ARQ | DATA | TX | [" + str(mycallsign, 'utf-8') + "]>> <<[" + str(static.DXCALLSIGN, - 'utf-8') + "]", - attempt=f"{str(attempt)}/{str(self.data_channel_max_retries)}") + self.log.info( + "[TNC] ARQ | DATA | TX | [" + + str(mycallsign, "UTF-8") + + "]>> <<[" + + str(static.DXCALLSIGN, "UTF-8") + + "]", + attempt=f"{str(attempt+1)}/{str(self.data_channel_max_retries)}", + ) self.enqueue_frame_for_tx(connection_frame) timeout = time.time() + 3 while time.time() < timeout: time.sleep(0.01) - # break if data channel is opened + # Stop waiting if data channel is opened if static.ARQ_STATE: - break + return True - if static.ARQ_STATE: - break + # `data_channel_max_retries` attempts have been sent. Aborting attempt & cleaning up + static.INFO.append("DATACHANNEL;FAILED") + self.log.debug( + "[TNC] arq_open_data_channel:", transmission_uuid=self.transmission_uuid + ) - if attempt == self.data_channel_max_retries: - static.INFO.append("DATACHANNEL;FAILED") - structlog.get_logger("structlog").debug("[TNC] arq_open_data_channel:", - transmission_uuid=self.transmission_uuid) - # print(self.transmission_uuid) - jsondata = {"arq": "transmission", "status": "failed", "uuid": self.transmission_uuid, - "percent": static.ARQ_TRANSMISSION_PERCENT, - "bytesperminute": static.ARQ_BYTES_PER_MINUTE} - json_data_out = json.dumps(jsondata) - sock.SOCKET_QUEUE.put(json_data_out) + jsondata = { + "arq": "transmission", + "status": "failed", + "uuid": self.transmission_uuid, + "percent": static.ARQ_TRANSMISSION_PERCENT, + "bytesperminute": static.ARQ_BYTES_PER_MINUTE, + } + json_data_out = json.dumps(jsondata) + sock.SOCKET_QUEUE.put(json_data_out) - structlog.get_logger("structlog").warning( - "[TNC] ARQ | TX | DATA [" + str(mycallsign, 'utf-8') + "]>>X<<[" + str(static.DXCALLSIGN, - 'utf-8') + "]") - self.datachannel_timeout = True - if not TESTMODE: - self.arq_cleanup() + self.log.warning( + "[TNC] ARQ | TX | DATA [" + + str(mycallsign, "UTF-8") + + "]>>X<<[" + + str(static.DXCALLSIGN, "UTF-8") + + "]" + ) + self.datachannel_timeout = True + self.arq_cleanup() - # attempt to cleanup the far-side, if it received the - # open_session frame and can still hear us. - self.close_session() - return False - # sys.exit() # close thread and so connection attempts + # attempt to cleanup the far-side, if it received the + # open_session frame and can still hear us. + self.close_session() + return False + + # Shouldn't get here.. + return True def arq_received_data_channel_opener(self, data_in: bytes): """ + Received request to open data channel framt Args: data_in:bytes: - Returns: - """ self.arq_file_transfer = True self.is_IRS = True @@ -1328,67 +1622,86 @@ class DATA: self.speed_level = len(self.mode_list) - 1 if 230 <= frametype <= 240: - structlog.get_logger("structlog").debug("[TNC] arq_received_data_channel_opener: manual mode request") - # print("manual mode request") + self.log.debug( + "[TNC] arq_received_data_channel_opener: manual mode request" + ) - # updated modes we are listening to + # Update modes we are listening to self.set_listening_modes(self.mode_list[self.speed_level]) - helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR, static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY) + helpers.add_to_heard_stations( + static.DXCALLSIGN, + static.DXGRID, + "DATA-CHANNEL", + static.SNR, + static.FREQ_OFFSET, + static.HAMLIB_FREQUENCY, + ) # check if callsign ssid override valid, mycallsign = helpers.check_callsign(self.mycallsign, data_in[1:4]) if not valid: # ARQ connect packet not for me. - if not TESTMODE: - self.arq_cleanup() + self.arq_cleanup() return - structlog.get_logger("structlog").info( - "[TNC] ARQ | DATA | RX | [" + str(mycallsign, 'utf-8') + "]>> <<[" + str(static.DXCALLSIGN, 'utf-8') + "]", - bandwith="wide") + self.log.info( + "[TNC] ARQ | DATA | RX | [" + + str(mycallsign, "UTF-8") + + "]>> <<[" + + str(static.DXCALLSIGN, "UTF-8") + + "]", + bandwith="wide", + ) static.ARQ_STATE = True - static.TNC_STATE = 'BUSY' + static.TNC_STATE = "BUSY" self.reset_statistics() + # Update the timeout timestamp self.data_channel_last_received = int(time.time()) - # check if we are in low bandwith mode + + # Select the frame type based on the mode we are in if static.LOW_BANDWITH_MODE or self.received_low_bandwith_mode: frametype = bytes([228]) - structlog.get_logger("structlog").debug("[TNC] Responding with low bandwidth mode") + self.log.debug("[TNC] Responding with low bandwidth mode") else: frametype = bytes([226]) - structlog.get_logger("structlog").debug("[TNC] Responding with high bandwidth mode") + self.log.debug("[TNC] Responding with high bandwidth mode") connection_frame = bytearray(14) connection_frame[:1] = frametype connection_frame[1:4] = static.DXCALLSIGN_CRC connection_frame[4:7] = static.MYCALLSIGN_CRC - connection_frame[13:14] = bytes([static.ARQ_PROTOCOL_VERSION]) # crc8 of version for checking protocol version + connection_frame[13:14] = bytes( + [static.ARQ_PROTOCOL_VERSION] + ) # crc8 of version for checking protocol version self.enqueue_frame_for_tx(connection_frame) - structlog.get_logger("structlog").info( - "[TNC] ARQ | DATA | RX | [" + str(mycallsign, 'utf-8') + "]>>|<<[" + str(static.DXCALLSIGN, 'utf-8') + "]", - bandwith="wide", snr=static.SNR) + self.log.info( + "[TNC] ARQ | DATA | RX | [" + + str(mycallsign, "UTF-8") + + "]>>|<<[" + + str(static.DXCALLSIGN, "UTF-8") + + "]", + bandwith="wide", + snr=static.SNR, + ) # set start of transmission for our statistics self.rx_start_of_transmission = time.time() - # reset our data channel watchdog + # Update the timeout timestamp self.data_channel_last_received = int(time.time()) - def arq_received_channel_is_open(self, data_in: bytes): + def arq_received_channel_is_open(self, data_in: bytes) -> None: """ Called if we received a data channel opener Args: data_in:bytes: - Returns: - """ protocol_version = int.from_bytes(bytes(data_in[13:14]), "big") if protocol_version == static.ARQ_PROTOCOL_VERSION: @@ -1400,49 +1713,65 @@ class DATA: self.mode_list = self.mode_list_low_bw self.time_list = self.time_list_low_bw self.speed_level = len(self.mode_list) - 1 - structlog.get_logger("structlog").debug("[TNC] low bandwidth mode", modes=self.mode_list) + self.log.debug("[TNC] low bandwidth mode", modes=self.mode_list) else: self.received_low_bandwith_mode = False self.mode_list = self.mode_list_high_bw self.time_list = self.time_list_high_bw self.speed_level = len(self.mode_list) - 1 - structlog.get_logger("structlog").debug("[TNC] high bandwidth mode", modes=self.mode_list) + self.log.debug("[TNC] high bandwidth mode", modes=self.mode_list) - helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'DATA-CHANNEL', static.SNR, - static.FREQ_OFFSET, static.HAMLIB_FREQUENCY) + helpers.add_to_heard_stations( + static.DXCALLSIGN, + static.DXGRID, + "DATA-CHANNEL", + static.SNR, + static.FREQ_OFFSET, + static.HAMLIB_FREQUENCY, + ) - structlog.get_logger("structlog").info( - "[TNC] ARQ | DATA | TX | [" + str(self.mycallsign, 'utf-8') + "]>>|<<[" + str(static.DXCALLSIGN, - 'utf-8') + "]", - snr=static.SNR) + self.log.info( + "[TNC] ARQ | DATA | TX | [" + + str(self.mycallsign, "UTF-8") + + "]>>|<<[" + + str(static.DXCALLSIGN, "UTF-8") + + "]", + snr=static.SNR, + ) # as soon as we set ARQ_STATE to DATA, transmission starts static.ARQ_STATE = True self.data_channel_last_received = int(time.time()) else: - static.TNC_STATE = 'IDLE' + static.TNC_STATE = "IDLE" static.ARQ_STATE = False static.INFO.append("PROTOCOL;VERSION_MISMATCH") - structlog.get_logger("structlog").warning("[TNC] protocol version mismatch:", received=protocol_version, - own=static.ARQ_PROTOCOL_VERSION) + self.log.warning( + "[TNC] protocol version mismatch:", + received=protocol_version, + own=static.ARQ_PROTOCOL_VERSION, + ) self.arq_cleanup() # ---------- PING - def transmit_ping(self, dxcallsign: bytes): + def transmit_ping(self, dxcallsign: bytes) -> None: """ Funktion for controlling pings Args: dxcallsign:bytes: - Returns: - """ static.DXCALLSIGN = dxcallsign static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN) static.INFO.append("PING;SENDING") - structlog.get_logger("structlog").info( - "[TNC] PING REQ [" + str(self.mycallsign, 'utf-8') + "] >>> [" + str(static.DXCALLSIGN, 'utf-8') + "]") + self.log.info( + "[TNC] PING REQ [" + + str(self.mycallsign, "UTF-8") + + "] >>> [" + + str(static.DXCALLSIGN, "UTF-8") + + "]" + ) ping_frame = bytearray(14) ping_frame[:1] = bytes([210]) @@ -1450,26 +1779,34 @@ class DATA: ping_frame[4:7] = static.MYCALLSIGN_CRC ping_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign) - structlog.get_logger("structlog").info("[TNC] ENABLE FSK", state=static.ENABLE_FSK) + self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK) if static.ENABLE_FSK: - self.enqueue_frame_for_tx(ping_frame, c2_mode='FSK_LDPC_0') + self.enqueue_frame_for_tx( + ping_frame, c2_mode=codec2.FREEDV_MODE.fsk_ldpc_0.value + ) else: - self.enqueue_frame_for_tx(ping_frame) + self.enqueue_frame_for_tx( + ping_frame, c2_mode=codec2.FREEDV_MODE.datac0.value + ) - def received_ping(self, data_in: bytes): + def received_ping(self, data_in: bytes) -> None: """ Called if we received a ping Args: data_in:bytes: - Returns: - """ static.DXCALLSIGN_CRC = bytes(data_in[4:7]) static.DXCALLSIGN = helpers.bytes_to_callsign(bytes(data_in[7:13])) - helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'PING', static.SNR, static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY) + helpers.add_to_heard_stations( + static.DXCALLSIGN, + static.DXGRID, + "PING", + static.SNR, + static.FREQ_OFFSET, + static.HAMLIB_FREQUENCY, + ) static.INFO.append("PING;RECEIVING") @@ -1477,13 +1814,18 @@ class DATA: valid, mycallsign = helpers.check_callsign(self.mycallsign, data_in[1:4]) if not valid: # PING packet not for me. - structlog.get_logger("structlog").debug("[TNC] received_ping: ping not for this station.") + self.log.debug("[TNC] received_ping: ping not for this station.") # print("ping not for me...") return - structlog.get_logger("structlog").info( - "[TNC] PING REQ [" + str(mycallsign, 'utf-8') + "] <<< [" + str(static.DXCALLSIGN, 'utf-8') + "]", - snr=static.SNR) + self.log.info( + "[TNC] PING REQ [" + + str(mycallsign, "UTF-8") + + "] <<< [" + + str(static.DXCALLSIGN, "UTF-8") + + "]", + snr=static.SNR, + ) ping_frame = bytearray(14) ping_frame[:1] = bytes([211]) @@ -1491,45 +1833,65 @@ class DATA: ping_frame[4:7] = static.MYCALLSIGN_CRC ping_frame[7:13] = static.MYGRID - structlog.get_logger("structlog").info("[TNC] ENABLE FSK", state=static.ENABLE_FSK) + self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK) if static.ENABLE_FSK: - self.enqueue_frame_for_tx(ping_frame, c2_mode='FSK_LDPC_0') + self.enqueue_frame_for_tx( + ping_frame, c2_mode=codec2.FREEDV_MODE.fsk_ldpc_0.value + ) else: - self.enqueue_frame_for_tx(ping_frame) + self.enqueue_frame_for_tx( + ping_frame, c2_mode=codec2.FREEDV_MODE.datac0.value + ) - def received_ping_ack(self, data_in: bytes): + def received_ping_ack(self, data_in: bytes) -> None: """ Called if a PING ack has been received Args: data_in:bytes: - Returns: - """ static.DXCALLSIGN_CRC = bytes(data_in[4:7]) - static.DXGRID = bytes(data_in[7:13]).rstrip(b'\x00') + static.DXGRID = bytes(data_in[7:13]).rstrip(b"\x00") - jsondata = {"type": "ping", "status": "ack", "uuid": str(uuid.uuid4()), "timestamp": int(time.time()), - "mycallsign": str(self.mycallsign, 'utf-8'), "dxcallsign": str(static.DXCALLSIGN, 'utf-8'), - "dxgrid": str(static.DXGRID, 'utf-8'), "snr": str(static.SNR)} + jsondata = { + "type": "ping", + "status": "ack", + "uuid": str(uuid.uuid4()), + "timestamp": int(time.time()), + "mycallsign": str(self.mycallsign, "UTF-8"), + "dxcallsign": str(static.DXCALLSIGN, "UTF-8"), + "dxgrid": str(static.DXGRID, "UTF-8"), + "snr": str(static.SNR), + } json_data_out = json.dumps(jsondata) sock.SOCKET_QUEUE.put(json_data_out) - helpers.add_to_heard_stations(static.DXCALLSIGN, static.DXGRID, 'PING-ACK', static.SNR, static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY) + helpers.add_to_heard_stations( + static.DXCALLSIGN, + static.DXGRID, + "PING-ACK", + static.SNR, + static.FREQ_OFFSET, + static.HAMLIB_FREQUENCY, + ) static.INFO.append("PING;RECEIVEDACK") - structlog.get_logger("structlog").info( - "[TNC] PING ACK [" + str(self.mycallsign, 'utf-8') + "] >|< [" + str(static.DXCALLSIGN, 'utf-8') + "]", - snr=static.SNR) - static.TNC_STATE = 'IDLE' + self.log.info( + "[TNC] PING ACK [" + + str(self.mycallsign, "UTF-8") + + "] >|< [" + + str(static.DXCALLSIGN, "UTF-8") + + "]", + snr=static.SNR, + ) + static.TNC_STATE = "IDLE" - def stop_transmission(self): + def stop_transmission(self) -> None: """ Force a stop of the running transmission """ - structlog.get_logger("structlog").warning("[TNC] Stopping transmission!") + self.log.warning("[TNC] Stopping transmission!") stop_frame = bytearray(14) stop_frame[:1] = bytes([249]) stop_frame[1:4] = static.DXCALLSIGN_CRC @@ -1538,23 +1900,23 @@ class DATA: self.enqueue_frame_for_tx(stop_frame, copies=2, repeat_delay=250) - static.TNC_STATE = 'IDLE' + static.TNC_STATE = "IDLE" static.ARQ_STATE = False static.INFO.append("TRANSMISSION;STOPPED") self.arq_cleanup() - def received_stop_transmission(self): + def received_stop_transmission(self) -> None: """ Received a transmission stop """ - structlog.get_logger("structlog").warning("[TNC] Stopping transmission!") - static.TNC_STATE = 'IDLE' + self.log.warning("[TNC] Stopping transmission!") + static.TNC_STATE = "IDLE" static.ARQ_STATE = False static.INFO.append("TRANSMISSION;STOPPED") self.arq_cleanup() # ----------- BROADCASTS - def run_beacon(self): + def run_beacon(self) -> None: """ Controlling function for running a beacon Args: @@ -1567,80 +1929,109 @@ class DATA: while 1: time.sleep(0.5) while static.BEACON_STATE: - if not static.ARQ_SESSION and not self.arq_file_transfer and not static.BEACON_PAUSE: + if ( + not static.ARQ_SESSION + and not self.arq_file_transfer + and not static.BEACON_PAUSE + ): static.INFO.append("BEACON;SENDING") - structlog.get_logger("structlog").info("[TNC] Sending beacon!", interval=self.beacon_interval) + self.log.info( + "[TNC] Sending beacon!", interval=self.beacon_interval + ) beacon_frame = bytearray(14) beacon_frame[:1] = bytes([250]) beacon_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign) beacon_frame[9:13] = static.MYGRID[:4] - structlog.get_logger("structlog").info("[TNC] ENABLE FSK", state=static.ENABLE_FSK) + self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK) if static.ENABLE_FSK: - self.enqueue_frame_for_tx(beacon_frame, c2_mode='FSK_LDPC_0') + self.enqueue_frame_for_tx( + beacon_frame, + c2_mode=codec2.freedv_get_mode_value_by_name( + "FSK_LDPC_0" + ), + ) else: self.enqueue_frame_for_tx(beacon_frame) interval_timer = time.time() + self.beacon_interval - while time.time() < interval_timer and static.BEACON_STATE and not static.BEACON_PAUSE: + while ( + time.time() < interval_timer + and static.BEACON_STATE + and not static.BEACON_PAUSE + ): time.sleep(0.01) except Exception as e: - structlog.get_logger("structlog").debug("[TNC] run_beacon: ", exception=e) + self.log.debug("[TNC] run_beacon: ", exception=e) # print(e) - def received_beacon(self, data_in: bytes): + def received_beacon(self, data_in: bytes) -> None: """ Called if we received a beacon Args: data_in:bytes: - Returns: - """ # here we add the received station to the heard stations buffer dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7])) - dxgrid = bytes(data_in[9:13]).rstrip(b'\x00') + dxgrid = bytes(data_in[9:13]).rstrip(b"\x00") - jsondata = {"type": "beacon", "status": "received", "uuid": str(uuid.uuid4()), "timestamp": int(time.time()), - "mycallsign": str(self.mycallsign, 'utf-8'), "dxcallsign": str(dxcallsign, 'utf-8'), - "dxgrid": str(dxgrid, 'utf-8'), "snr": str(static.SNR)} + jsondata = { + "type": "beacon", + "status": "received", + "uuid": str(uuid.uuid4()), + "timestamp": int(time.time()), + "mycallsign": str(self.mycallsign, "UTF-8"), + "dxcallsign": str(dxcallsign, "UTF-8"), + "dxgrid": str(dxgrid, "UTF-8"), + "snr": str(static.SNR), + } json_data_out = json.dumps(jsondata) sock.SOCKET_QUEUE.put(json_data_out) static.INFO.append("BEACON;RECEIVING") - structlog.get_logger("structlog").info( - "[TNC] BEACON RCVD [" + str(dxcallsign, 'utf-8') + "][" + str(dxgrid, 'utf-8') + "] ", snr=static.SNR) - helpers.add_to_heard_stations(dxcallsign, dxgrid, 'BEACON', static.SNR, static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY) + self.log.info( + "[TNC] BEACON RCVD [" + + str(dxcallsign, "UTF-8") + + "][" + + str(dxgrid, "UTF-8") + + "] ", + snr=static.SNR, + ) + helpers.add_to_heard_stations( + dxcallsign, + dxgrid, + "BEACON", + static.SNR, + static.FREQ_OFFSET, + static.HAMLIB_FREQUENCY, + ) - def transmit_cq(self): + def transmit_cq(self) -> None: """ Transmit a CQ - Args: - self - - Returns: - Nothing """ - structlog.get_logger("structlog").info("[TNC] CQ CQ CQ") + self.log.info("[TNC] CQ CQ CQ") static.INFO.append("CQ;SENDING") cq_frame = bytearray(14) cq_frame[:1] = bytes([200]) cq_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign) - cq_frame[7:11] = helpers.encode_grid(static.MYGRID.decode("utf-8")) + cq_frame[7:11] = helpers.encode_grid(static.MYGRID.decode("UTF-8")) - structlog.get_logger("structlog").info("[TNC] ENABLE FSK", state=static.ENABLE_FSK) - structlog.get_logger("structlog").debug("[TNC] CQ Frame:", data=[cq_frame]) + self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK) + self.log.debug("[TNC] CQ Frame:", data=[cq_frame]) if static.ENABLE_FSK: - self.enqueue_frame_for_tx(cq_frame, c2_mode='FSK_LDPC_0') + self.enqueue_frame_for_tx( + cq_frame, c2_mode=codec2.freedv_get_mode_value_by_name("FSK_LDPC_0") + ) else: self.enqueue_frame_for_tx(cq_frame) - def received_cq(self, data_in: bytes): + def received_cq(self, data_in: bytes) -> None: """ Called when we receive a CQ frame Args: @@ -1651,26 +2042,36 @@ class DATA: """ # here we add the received station to the heard stations buffer dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7])) - structlog.get_logger("structlog").debug("[TNC] received_cq:", dxcallsign=dxcallsign) + self.log.debug("[TNC] received_cq:", dxcallsign=dxcallsign) # print(dxcallsign) - dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "utf-8") + dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8") static.INFO.append("CQ;RECEIVING") - structlog.get_logger("structlog").info( - "[TNC] CQ RCVD [" + str(dxcallsign, 'utf-8') + "][" + str(dxgrid, 'utf-8') + "] ", snr=static.SNR) - helpers.add_to_heard_stations(dxcallsign, dxgrid, 'CQ CQ CQ', static.SNR, static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY) + self.log.info( + "[TNC] CQ RCVD [" + + str(dxcallsign, "UTF-8") + + "][" + + str(dxgrid, "UTF-8") + + "] ", + snr=static.SNR, + ) + helpers.add_to_heard_stations( + dxcallsign, + dxgrid, + "CQ CQ CQ", + static.SNR, + static.FREQ_OFFSET, + static.HAMLIB_FREQUENCY, + ) if static.RESPOND_TO_CQ: self.transmit_qrv() - def transmit_qrv(self): + def transmit_qrv(self) -> None: """ Called when we send a QRV frame Args: self - Returns: - Nothing """ # Sleep a random amount of time before responding to make it more likely to be # heard when many stations respond. Each DATAC0 frame is 0.44 sec (440ms) in @@ -1678,47 +2079,68 @@ class DATA: # in 0.5s increments. helpers.wait(randrange(0, 20, 5) / 10.0) static.INFO.append("QRV;SENDING") - structlog.get_logger("structlog").info("[TNC] Sending QRV!") + self.log.info("[TNC] Sending QRV!") qrv_frame = bytearray(14) qrv_frame[:1] = bytes([201]) qrv_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign) - qrv_frame[7:11] = helpers.encode_grid(static.MYGRID.decode("utf-8")) + qrv_frame[7:11] = helpers.encode_grid(static.MYGRID.decode("UTF-8")) - structlog.get_logger("structlog").info("[TNC] ENABLE FSK", state=static.ENABLE_FSK) + self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK) if static.ENABLE_FSK: - self.enqueue_frame_for_tx(qrv_frame, c2_mode='FSK_LDPC_0') + self.enqueue_frame_for_tx( + qrv_frame, c2_mode=codec2.freedv_get_mode_value_by_name("FSK_LDPC_0") + ) else: self.enqueue_frame_for_tx(qrv_frame) - def received_qrv(self, data_in: bytes): + def received_qrv(self, data_in: bytes) -> None: """ Called when we receive a QRV frame Args: data_in:bytes: - Returns: - Nothing """ # here we add the received station to the heard stations buffer dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7])) - dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "utf-8") + dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8") - jsondata = {"type": "qrv", "status": "received", "uuid": str(uuid.uuid4()), "timestamp": int(time.time()), - "mycallsign": str(self.mycallsign, 'utf-8'), "dxcallsign": str(dxcallsign, 'utf-8'), - "dxgrid": str(dxgrid, 'utf-8'), "snr": str(static.SNR)} + jsondata = { + "type": "qrv", + "status": "received", + "uuid": str(uuid.uuid4()), + "timestamp": int(time.time()), + "mycallsign": str(self.mycallsign, "UTF-8"), + "dxcallsign": str(dxcallsign, "UTF-8"), + "dxgrid": str(dxgrid, "UTF-8"), + "snr": str(static.SNR), + } json_data_out = json.dumps(jsondata) sock.SOCKET_QUEUE.put(json_data_out) static.INFO.append("QRV;RECEIVING") - structlog.get_logger("structlog").info( - "[TNC] QRV RCVD [" + str(dxcallsign, 'utf-8') + "][" + str(dxgrid, 'utf-8') + "] ", snr=static.SNR) - helpers.add_to_heard_stations(dxcallsign, dxgrid, 'QRV', static.SNR, static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY) + self.log.info( + "[TNC] QRV RCVD [" + + str(dxcallsign, "UTF-8") + + "][" + + str(dxgrid, "UTF-8") + + "] ", + snr=static.SNR, + ) + helpers.add_to_heard_stations( + dxcallsign, + dxgrid, + "QRV", + static.SNR, + static.FREQ_OFFSET, + static.HAMLIB_FREQUENCY, + ) # ------------ CALUCLATE TRANSFER RATES - def calculate_transfer_rate_rx(self, rx_start_of_transmission: float, receivedbytes: int) -> list: + def calculate_transfer_rate_rx( + self, rx_start_of_transmission: float, receivedbytes: int + ) -> list: """ Calculate transfer rate for received data Args: @@ -1734,28 +2156,41 @@ class DATA: if static.TOTAL_BYTES == 0: static.TOTAL_BYTES = 1 static.ARQ_TRANSMISSION_PERCENT = min( - int((receivedbytes * static.ARQ_COMPRESSION_FACTOR / (static.TOTAL_BYTES)) * 100), 100) + int( + ( + receivedbytes + * static.ARQ_COMPRESSION_FACTOR + / (static.TOTAL_BYTES) + ) + * 100 + ), + 100, + ) transmissiontime = time.time() - self.rx_start_of_transmission if receivedbytes > 0: static.ARQ_BITS_PER_SECOND = int((receivedbytes * 8) / transmissiontime) - static.ARQ_BYTES_PER_MINUTE = int((receivedbytes) / (transmissiontime / 60)) + static.ARQ_BYTES_PER_MINUTE = int( + (receivedbytes) / (transmissiontime / 60) + ) else: static.ARQ_BITS_PER_SECOND = 0 static.ARQ_BYTES_PER_MINUTE = 0 except Exception as e: - structlog.get_logger("structlog").error(f"[TNC] calculate_transfer_rate_rx: Exception: {e}") + self.log.error(f"[TNC] calculate_transfer_rate_rx: Exception: {e}") static.ARQ_TRANSMISSION_PERCENT = 0.0 static.ARQ_BITS_PER_SECOND = 0 static.ARQ_BYTES_PER_MINUTE = 0 - return [static.ARQ_BITS_PER_SECOND, - static.ARQ_BYTES_PER_MINUTE, - static.ARQ_TRANSMISSION_PERCENT] + return [ + static.ARQ_BITS_PER_SECOND, + static.ARQ_BYTES_PER_MINUTE, + static.ARQ_TRANSMISSION_PERCENT, + ] - def reset_statistics(self): + def reset_statistics(self) -> None: """ Reset statistics """ @@ -1767,8 +2202,9 @@ class DATA: static.ARQ_TRANSMISSION_PERCENT = 0 static.TOTAL_BYTES = 0 - def calculate_transfer_rate_tx(self, tx_start_of_transmission: float, sentbytes: int, - tx_buffer_length: int) -> list: + def calculate_transfer_rate_tx( + self, tx_start_of_transmission: float, sentbytes: int, tx_buffer_length: int + ) -> list: """ Calcualte Transferrate for transmission Args: @@ -1776,40 +2212,53 @@ class DATA: sentbytes:int: tx_buffer_length:int: - Returns: - + Returns: List of: + bits_per_second: float, + bytes_per_minute: float, + transmission_percent: float """ try: - static.ARQ_TRANSMISSION_PERCENT = min(int((sentbytes / tx_buffer_length) * 100), 100) + static.ARQ_TRANSMISSION_PERCENT = min( + int((sentbytes / tx_buffer_length) * 100), 100 + ) transmissiontime = time.time() - tx_start_of_transmission if sentbytes > 0: - static.ARQ_BITS_PER_SECOND = int((sentbytes * 8) / transmissiontime) # Bits per Second - static.ARQ_BYTES_PER_MINUTE = int((sentbytes) / (transmissiontime / 60)) # Bytes per Minute + static.ARQ_BITS_PER_SECOND = int( + (sentbytes * 8) / transmissiontime + ) # Bits per Second + static.ARQ_BYTES_PER_MINUTE = int( + (sentbytes) / (transmissiontime / 60) + ) # Bytes per Minute else: static.ARQ_BITS_PER_SECOND = 0 static.ARQ_BYTES_PER_MINUTE = 0 except Exception as e: - structlog.get_logger("structlog").error(f"[TNC] calculate_transfer_rate_tx: Exception: {e}") + self.log.error(f"[TNC] calculate_transfer_rate_tx: Exception: {e}") static.ARQ_TRANSMISSION_PERCENT = 0.0 static.ARQ_BITS_PER_SECOND = 0 static.ARQ_BYTES_PER_MINUTE = 0 - return [static.ARQ_BITS_PER_SECOND, - static.ARQ_BYTES_PER_MINUTE, - static.ARQ_TRANSMISSION_PERCENT] + return [ + static.ARQ_BITS_PER_SECOND, + static.ARQ_BYTES_PER_MINUTE, + static.ARQ_TRANSMISSION_PERCENT, + ] # ----------------------CLEANUP AND RESET FUNCTIONS - def arq_cleanup(self): + def arq_cleanup(self) -> None: """ - Cleanup funktion which clears all ARQ states + Cleanup function which clears all ARQ states """ - structlog.get_logger("structlog").debug("[TNC] arq_cleanup") + if TESTMODE: + return - self.received_mycall_crc = b'' + self.log.debug("[TNC] arq_cleanup") + + self.received_mycall_crc = b"" self.rx_frame_bof_received = False self.rx_frame_eof_received = False @@ -1817,7 +2266,7 @@ class DATA: self.rpt_request_received = False self.data_frame_ack_received = False static.RX_BURST_BUFFER = [] - static.RX_FRAME_BUFFER = b'' + static.RX_FRAME_BUFFER = b"" self.burst_ack_snr = 255 # reset modem receiving state to reduce cpu load @@ -1843,63 +2292,57 @@ class DATA: self.n_retries_per_burst = 0 if not static.ARQ_SESSION: - static.TNC_STATE = 'IDLE' + static.TNC_STATE = "IDLE" static.ARQ_STATE = False self.arq_file_transfer = False static.BEACON_PAUSE = False - def arq_reset_ack(self, state: bool): + def arq_reset_ack(self, state: bool) -> None: """ Funktion for resetting acknowledge states Args: state:bool: - Returns: - """ self.burst_ack = state self.rpt_request_received = state self.data_frame_ack_received = state - def set_listening_modes(self, mode): + def set_listening_modes(self, mode: int) -> None: """ Function for setting the data modes we are listening to for saving cpu power Args: - mode: - - Returns: + mode:int: Codec2 mode to listen for """ # set modes we want to listen to mode_name = codec2.freedv_get_mode_name_by_value(mode) - if mode_name == 'datac1': + if mode_name == "datac1": modem.RECEIVE_DATAC1 = True - structlog.get_logger("structlog").debug("[TNC] Changing listening data mode", mode="datac1") - elif mode_name == 'datac3': + self.log.debug("[TNC] Changing listening data mode", mode="datac1") + elif mode_name == "datac3": modem.RECEIVE_DATAC3 = True - structlog.get_logger("structlog").debug("[TNC] Changing listening data mode", mode="datac3") - elif mode_name == 'fsk_ldpc_1': + self.log.debug("[TNC] Changing listening data mode", mode="datac3") + elif mode_name == "fsk_ldpc_1": modem.RECEIVE_FSK_LDPC_1 = True - structlog.get_logger("structlog").debug("[TNC] Changing listening data mode", mode="fsk_ldpc_1") - elif mode_name == 'allmodes': + self.log.debug("[TNC] Changing listening data mode", mode="fsk_ldpc_1") + elif mode_name == "allmodes": modem.RECEIVE_DATAC1 = True modem.RECEIVE_DATAC3 = True modem.RECEIVE_FSK_LDPC_1 = True - structlog.get_logger("structlog").debug("[TNC] Changing listening data mode", mode="datac1/datac3/fsk_ldpc") + self.log.debug( + "[TNC] Changing listening data mode", mode="datac1/datac3/fsk_ldpc" + ) # ------------------------- WATCHDOG FUNCTIONS FOR TIMER - def watchdog(self): + def watchdog(self) -> None: """Author: DJ2LS - Watchdog master function. From here, call the watchdogs - - Args: - - Returns: + Watchdog master function. From here, "pet" the watchdogs """ while True: @@ -1908,24 +2351,32 @@ class DATA: self.burst_watchdog() self.arq_session_keep_alive_watchdog() - def burst_watchdog(self): + def burst_watchdog(self) -> None: """ - watchdog which checks if we are running into a connection timeout + Watchdog which checks if we are running into a connection timeout DATA BURST """ # IRS SIDE # TODO: We need to redesign this part for cleaner state handling - # return only if not ARQ STATE and not ARQ SESSION STATE as they are different use cases - if not static.ARQ_STATE and static.ARQ_SESSION_STATE != 'connected' or not self.is_IRS: + # Return if not ARQ STATE and not ARQ SESSION STATE as they are different use cases + if ( + not static.ARQ_STATE + and static.ARQ_SESSION_STATE != "connected" + or not self.is_IRS + ): return - # we want to reach this state only if connected ( == return above not called ) - if self.data_channel_last_received + self.time_list[self.speed_level] > time.time(): - # print((self.data_channel_last_received + self.time_list[self.speed_level])-time.time()) - pass - else: - structlog.get_logger("structlog").warning("[TNC] Frame timeout", attempt=self.n_retries_per_burst, - max_attempts=self.rx_n_max_retries_per_burst, - speed_level=self.speed_level) + + # We want to reach this state only if connected ( == return above not called ) + if ( + self.data_channel_last_received + self.time_list[self.speed_level] + <= time.time() + ): + self.log.warning( + "[TNC] Frame timeout", + attempt=self.n_retries_per_burst, + max_attempts=self.rx_n_max_retries_per_burst, + speed_level=self.speed_level, + ) self.frame_received_counter = 0 self.burst_nack_counter += 1 if self.burst_nack_counter >= 2: @@ -1938,63 +2389,89 @@ class DATA: self.speed_level = 0 static.ARQ_SPEED_LEVEL = self.speed_level - # updated modes we are listening to + # Update modes we are listening to self.set_listening_modes(self.mode_list[self.speed_level]) - self.send_burst_nack_frame_watchdog(0) # Why not pass `snr`? + self.send_burst_nack_frame_watchdog( + 0 + ) # Why not pass `snr` or `static.SNR`? self.data_channel_last_received = time.time() self.n_retries_per_burst += 1 + else: + # print((self.data_channel_last_received + self.time_list[self.speed_level])-time.time()) + pass if self.n_retries_per_burst >= self.rx_n_max_retries_per_burst: self.stop_transmission() self.arq_cleanup() - def data_channel_keep_alive_watchdog(self): + def data_channel_keep_alive_watchdog(self) -> None: """ watchdog which checks if we are running into a connection timeout DATA CHANNEL """ # and not static.ARQ_SEND_KEEP_ALIVE: - if static.ARQ_STATE and static.TNC_STATE == 'BUSY': + if static.ARQ_STATE and static.TNC_STATE == "BUSY": time.sleep(0.01) - if self.data_channel_last_received + self.transmission_timeout > time.time(): + if ( + self.data_channel_last_received + self.transmission_timeout + > time.time() + ): time.sleep(0.01) # print(self.data_channel_last_received + self.transmission_timeout - time.time()) # pass else: self.data_channel_last_received = 0 - structlog.get_logger("structlog").info( - "[TNC] DATA [" + str(self.mycallsign, 'utf-8') + "]<>[" + str(static.DXCALLSIGN, 'utf-8') + "]") + self.log.info( + "[TNC] DATA [" + + str(self.mycallsign, "UTF-8") + + "]<>[" + + str(static.DXCALLSIGN, "UTF-8") + + "]" + ) static.INFO.append("ARQ;RECEIVING;FAILED") - if not TESTMODE: - self.arq_cleanup() + self.arq_cleanup() - def arq_session_keep_alive_watchdog(self): + def arq_session_keep_alive_watchdog(self) -> None: """ watchdog which checks if we are running into a connection timeout ARQ SESSION """ - if static.ARQ_SESSION and static.TNC_STATE == 'BUSY' and not self.arq_file_transfer: + if ( + static.ARQ_SESSION + and static.TNC_STATE == "BUSY" + and not self.arq_file_transfer + ): if self.arq_session_last_received + self.arq_session_timeout > time.time(): time.sleep(0.01) else: - structlog.get_logger("structlog").info( - "[TNC] SESSION [" + str(self.mycallsign, 'utf-8') + "]<>[" + str(static.DXCALLSIGN, - 'utf-8') + "]") + self.log.info( + "[TNC] SESSION [" + + str(self.mycallsign, "UTF-8") + + "]<>[" + + str(static.DXCALLSIGN, "UTF-8") + + "]" + ) static.INFO.append("ARQ;SESSION;TIMEOUT") self.close_session() - def heartbeat(self): + def heartbeat(self) -> None: """ heartbeat thread which auto resumes the heartbeat signal within a arq session """ while 1: time.sleep(0.01) - if static.ARQ_SESSION and self.IS_ARQ_SESSION_MASTER and static.ARQ_SESSION_STATE == "connected" and not self.arq_file_transfer: + if ( + static.ARQ_SESSION + and self.IS_ARQ_SESSION_MASTER + and static.ARQ_SESSION_STATE == "connected" + and not self.arq_file_transfer + ): time.sleep(1) self.transmit_session_heartbeat() time.sleep(2) - def send_test_frame(self): - modem.MODEM_TRANSMIT_QUEUE.put([12, 1, 0, [bytearray(126)]]) + def send_test_frame(self) -> None: + """Send a test frame (type 12) frame""" + self.enqueue_frame_for_tx(frame_to_tx=bytearray(126), c2_mode=12) From ee1305a2dc6d0cb2db81d7b7ad020d5f3b895ba9 Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Sun, 22 May 2022 21:11:40 -0400 Subject: [PATCH 07/26] Bandwidth spelling. --- tnc/data_handler.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tnc/data_handler.py b/tnc/data_handler.py index 1696acb5..efe2a4ef 100644 --- a/tnc/data_handler.py +++ b/tnc/data_handler.py @@ -75,8 +75,8 @@ class DATA: self.rx_n_max_retries_per_burst = 50 self.n_retries_per_burst = 0 - # Flag to indicate if we recevied a low bandwith mode channel opener - self.received_low_bandwith_mode = False + # Flag to indicate if we recevied a low bandwidth mode channel opener + self.received_low_bandwidth_mode = False self.data_channel_max_retries = 5 self.datachannel_timeout = False @@ -1610,13 +1610,13 @@ class DATA: n_frames_per_burst = int.from_bytes(bytes(data_in[13:14]), "big") frametype = int.from_bytes(bytes(data_in[:1]), "big") - # check if we received low bandwith mode + # check if we received low bandwidth mode if frametype == 225: - self.received_low_bandwith_mode = False + self.received_low_bandwidth_mode = False self.mode_list = self.mode_list_high_bw self.time_list = self.time_list_high_bw else: - self.received_low_bandwith_mode = True + self.received_low_bandwidth_mode = True self.mode_list = self.mode_list_low_bw self.time_list = self.time_list_low_bw self.speed_level = len(self.mode_list) - 1 @@ -1651,7 +1651,7 @@ class DATA: + "]>> <<[" + str(static.DXCALLSIGN, "UTF-8") + "]", - bandwith="wide", + bandwidth="wide", ) static.ARQ_STATE = True @@ -1663,7 +1663,7 @@ class DATA: self.data_channel_last_received = int(time.time()) # Select the frame type based on the mode we are in - if static.LOW_BANDWITH_MODE or self.received_low_bandwith_mode: + if static.LOW_BANDWITH_MODE or self.received_low_bandwidth_mode: frametype = bytes([228]) self.log.debug("[TNC] Responding with low bandwidth mode") else: @@ -1686,7 +1686,7 @@ class DATA: + "]>>|<<[" + str(static.DXCALLSIGN, "UTF-8") + "]", - bandwith="wide", + bandwidth="wide", snr=static.SNR, ) @@ -1709,13 +1709,13 @@ class DATA: frametype = int.from_bytes(bytes(data_in[:1]), "big") if frametype == 228: - self.received_low_bandwith_mode = True + self.received_low_bandwidth_mode = True self.mode_list = self.mode_list_low_bw self.time_list = self.time_list_low_bw self.speed_level = len(self.mode_list) - 1 self.log.debug("[TNC] low bandwidth mode", modes=self.mode_list) else: - self.received_low_bandwith_mode = False + self.received_low_bandwidth_mode = False self.mode_list = self.mode_list_high_bw self.time_list = self.time_list_high_bw self.speed_level = len(self.mode_list) - 1 @@ -2285,8 +2285,8 @@ class DATA: self.speed_level = len(self.mode_list) - 1 static.ARQ_SPEED_LEVEL = self.speed_level - # low bandwith mode indicator - self.received_low_bandwith_mode = False + # low bandwidth mode indicator + self.received_low_bandwidth_mode = False # reset retry counter for rx channel / burst self.n_retries_per_burst = 0 From a456ff54f7957ed4de02611068530e766a49bf10 Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Sun, 22 May 2022 22:13:43 -0400 Subject: [PATCH 08/26] Use min/max for speed_level adjustments. Rearranged more comments. --- tnc/data_handler.py | 108 +++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 66 deletions(-) diff --git a/tnc/data_handler.py b/tnc/data_handler.py index efe2a4ef..d5e00e33 100644 --- a/tnc/data_handler.py +++ b/tnc/data_handler.py @@ -506,7 +506,7 @@ class DATA: static.ARQ_STATE = True static.INFO.append("ARQ;RECEIVING") - # Update the timeout timestamp + # Update data_channel timestamp self.data_channel_last_received = int(time.time()) # Extract some important data from the frame @@ -598,10 +598,10 @@ class DATA: self.frame_received_counter += 1 if self.frame_received_counter >= 2: self.frame_received_counter = 0 - self.speed_level += 1 - if self.speed_level >= len(self.mode_list): - self.speed_level = len(self.mode_list) - 1 - static.ARQ_SPEED_LEVEL = self.speed_level + self.speed_level = min( + self.speed_level + 1, len(self.mode_list) - 1 + ) + static.ARQ_SPEED_LEVEL = self.speed_level # Update modes we are listening to self.set_listening_modes(self.mode_list[self.speed_level]) @@ -655,9 +655,8 @@ class DATA: frame_length = int.from_bytes(payload[4:8], "big") # 4:8 4bytes static.TOTAL_BYTES = frame_length compression_factor = int.from_bytes(payload[8:9], "big") # 4:8 4bytes - compression_factor = np.clip( - compression_factor, 0, 255 - ) # limit to max value of 255 + # limit to max value of 255 + compression_factor = np.clip(compression_factor, 0, 255) static.ARQ_COMPRESSION_FACTOR = compression_factor / 10 self.calculate_transfer_rate_rx( self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER) @@ -765,7 +764,7 @@ class DATA: self.log.info("[TNC] ARQ | RX | Sending NACK") self.send_burst_nack_frame(snr) - # Update timeout timestamp + # Update arq_session timestamp self.arq_session_last_received = int(time.time()) # Finally cleanup our buffers and states, @@ -852,7 +851,6 @@ class DATA: and not self.data_frame_ack_received and static.ARQ_STATE ): - # we have TX_N_MAX_RETRIES_PER_BURST attempts for sending a burst for self.tx_n_retry_of_burst in range(TX_N_MAX_RETRIES_PER_BURST): # AUTO MODE SELECTION @@ -937,10 +935,6 @@ class DATA: self.enqueue_frame_for_tx(bytearray(tempbuffer), c2_mode=data_mode) - # wait while transmitting - while static.TRANSMITTING: - time.sleep(0.01) - # After transmission finished, wait for an ACK or RPT frame # burstacktimeout = time.time() + BURST_ACK_TIMEOUT_SECONDS + 100 # while (not self.burst_ack and not self.burst_nack and @@ -1081,7 +1075,7 @@ class DATA: ) # Force data loops of TNC to stop and continue with next frame self.burst_ack = True - # Update our timeout timestamp + # Update data_channel timestamp self.data_channel_last_received = int(time.time()) self.burst_ack_snr = int.from_bytes(bytes(data_in[7:8]), "big") self.speed_level = int.from_bytes(bytes(data_in[8:9]), "big") @@ -1116,12 +1110,10 @@ class DATA: static.FREQ_OFFSET, static.HAMLIB_FREQUENCY, ) - self.burst_nack = ( - True # Force data loops of TNC to stop and continue with next frame - ) - self.data_channel_last_received = int( - time.time() - ) # we need to update our timeout timestamp + # Force data loops of TNC to stop and continue with next frame + self.burst_nack = True + # Update data_channel timestamp + self.data_channel_last_received = int(time.time()) self.burst_ack_snr = int.from_bytes(bytes(data_in[7:8]), "big") self.speed_level = int.from_bytes(bytes(data_in[8:9]), "big") static.ARQ_SPEED_LEVEL = self.speed_level @@ -1140,15 +1132,11 @@ class DATA: static.FREQ_OFFSET, static.HAMLIB_FREQUENCY, ) - self.data_frame_ack_received = ( - True # Force data loops of TNC to stop and continue with next frame - ) - self.data_channel_last_received = int( - time.time() - ) # we need to update our timeout timestamp - self.arq_session_last_received = int( - time.time() - ) # we need to update our timeout timestamp + # Force data loops of TNC to stop and continue with next frame + self.data_frame_ack_received = True + # Update arq_session and data_channel timestamp + self.data_channel_last_received = int(time.time()) + self.arq_session_last_received = int(time.time()) def frame_nack_received(self, data_in: bytes): # pylint: disable=unused-argument """ @@ -1176,7 +1164,7 @@ class DATA: } json_data_out = json.dumps(jsondata) sock.SOCKET_QUEUE.put(json_data_out) - # Update the timeout timestamp + # Update data_channel timestamp self.arq_session_last_received = int(time.time()) self.arq_cleanup() @@ -1201,9 +1189,8 @@ class DATA: ) self.rpt_request_received = True - self.data_channel_last_received = int( - time.time() - ) # we need to update our timeout timestamp + # Update data_channel timestamp + self.data_channel_last_received = int(time.time()) self.rpt_request_buffer = [] missing_area = bytes(data_in[3:12]) # 1:9 @@ -1271,14 +1258,14 @@ class DATA: while not static.ARQ_SESSION: time.sleep(0.01) - for attempt in range(1, self.session_connect_max_retries + 1): + for attempt in range(self.session_connect_max_retries): self.log.info( "[TNC] SESSION [" + str(self.mycallsign, "UTF-8") + "]>>?<<[" + str(static.DXCALLSIGN, "UTF-8") + "]", - a=attempt, + a=attempt + 1, state=static.ARQ_SESSION_STATE, ) @@ -1314,7 +1301,7 @@ class DATA: self.IS_ARQ_SESSION_MASTER = False static.ARQ_SESSION_STATE = "connecting" - # Update the timeout timestamp + # Update arq_session timestamp self.arq_session_last_received = int(time.time()) static.DXCALLSIGN_CRC = bytes(data_in[4:7]) @@ -1516,7 +1503,7 @@ class DATA: """ self.is_IRS = False - # Update the timeout timestamp + # Update data_channel timestamp self.data_channel_last_received = int(time.time()) if static.LOW_BANDWITH_MODE and mode == 255: @@ -1586,7 +1573,7 @@ class DATA: self.datachannel_timeout = True self.arq_cleanup() - # attempt to cleanup the far-side, if it received the + # Attempt to cleanup the far-side, if it received the # open_session frame and can still hear us. self.close_session() return False @@ -1659,7 +1646,7 @@ class DATA: self.reset_statistics() - # Update the timeout timestamp + # Update data_channel timestamp self.data_channel_last_received = int(time.time()) # Select the frame type based on the mode we are in @@ -1674,9 +1661,8 @@ class DATA: connection_frame[:1] = frametype connection_frame[1:4] = static.DXCALLSIGN_CRC connection_frame[4:7] = static.MYCALLSIGN_CRC - connection_frame[13:14] = bytes( - [static.ARQ_PROTOCOL_VERSION] - ) # crc8 of version for checking protocol version + # For checking protocol version on the receiving side + connection_frame[13:14] = bytes([static.ARQ_PROTOCOL_VERSION]) self.enqueue_frame_for_tx(connection_frame) @@ -1693,7 +1679,7 @@ class DATA: # set start of transmission for our statistics self.rx_start_of_transmission = time.time() - # Update the timeout timestamp + # Update data_channel timestamp self.data_channel_last_received = int(time.time()) def arq_received_channel_is_open(self, data_in: bytes) -> None: @@ -1712,14 +1698,13 @@ class DATA: self.received_low_bandwidth_mode = True self.mode_list = self.mode_list_low_bw self.time_list = self.time_list_low_bw - self.speed_level = len(self.mode_list) - 1 self.log.debug("[TNC] low bandwidth mode", modes=self.mode_list) else: self.received_low_bandwidth_mode = False self.mode_list = self.mode_list_high_bw self.time_list = self.time_list_high_bw - self.speed_level = len(self.mode_list) - 1 self.log.debug("[TNC] high bandwidth mode", modes=self.mode_list) + self.speed_level = len(self.mode_list) - 1 helpers.add_to_heard_stations( static.DXCALLSIGN, @@ -1741,6 +1726,7 @@ class DATA: # as soon as we set ARQ_STATE to DATA, transmission starts static.ARQ_STATE = True + # Update data_channel timestamp self.data_channel_last_received = int(time.time()) else: static.TNC_STATE = "IDLE" @@ -1815,7 +1801,6 @@ class DATA: if not valid: # PING packet not for me. self.log.debug("[TNC] received_ping: ping not for this station.") - # print("ping not for me...") return self.log.info( @@ -1965,7 +1950,6 @@ class DATA: except Exception as e: self.log.debug("[TNC] run_beacon: ", exception=e) - # print(e) def received_beacon(self, data_in: bytes) -> None: """ @@ -2043,7 +2027,7 @@ class DATA: # here we add the received station to the heard stations buffer dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7])) self.log.debug("[TNC] received_cq:", dxcallsign=dxcallsign) - # print(dxcallsign) + dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8") static.INFO.append("CQ;RECEIVING") self.log.info( @@ -2225,12 +2209,8 @@ class DATA: transmissiontime = time.time() - tx_start_of_transmission if sentbytes > 0: - static.ARQ_BITS_PER_SECOND = int( - (sentbytes * 8) / transmissiontime - ) # Bits per Second - static.ARQ_BYTES_PER_MINUTE = int( - (sentbytes) / (transmissiontime / 60) - ) # Bytes per Minute + static.ARQ_BITS_PER_SECOND = int((sentbytes * 8) / transmissiontime) + static.ARQ_BYTES_PER_MINUTE = int((sentbytes) / (transmissiontime / 60)) else: static.ARQ_BITS_PER_SECOND = 0 @@ -2380,22 +2360,17 @@ class DATA: self.frame_received_counter = 0 self.burst_nack_counter += 1 if self.burst_nack_counter >= 2: - self.speed_level -= 1 - # print(self.burst_nack_counter) - # print(self.speed_level) - static.ARQ_SPEED_LEVEL = self.speed_level self.burst_nack_counter = 0 - if self.speed_level <= 0: - self.speed_level = 0 + self.speed_level = max(self.speed_level - 1, 0) static.ARQ_SPEED_LEVEL = self.speed_level # Update modes we are listening to self.set_listening_modes(self.mode_list[self.speed_level]) - self.send_burst_nack_frame_watchdog( - 0 - ) # Why not pass `snr` or `static.SNR`? + # Why not pass `snr` or `static.SNR`? + self.send_burst_nack_frame_watchdog(0) + # Update data_channel timestamp self.data_channel_last_received = time.time() self.n_retries_per_burst += 1 else: @@ -2422,6 +2397,7 @@ class DATA: # print(self.data_channel_last_received + self.transmission_timeout - time.time()) # pass else: + # Clear the timeout timestamp self.data_channel_last_received = 0 self.log.info( "[TNC] DATA [" @@ -2458,7 +2434,7 @@ class DATA: def heartbeat(self) -> None: """ - heartbeat thread which auto resumes the heartbeat signal within a arq session + Heartbeat thread which auto resumes the heartbeat signal within an arq session """ while 1: time.sleep(0.01) @@ -2473,5 +2449,5 @@ class DATA: time.sleep(2) def send_test_frame(self) -> None: - """Send a test frame (type 12) frame""" + """Send a test (type 12) frame""" self.enqueue_frame_for_tx(frame_to_tx=bytearray(126), c2_mode=12) From 9c28021c51dea6d8559b20ec58fa364c99fc0823 Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Mon, 23 May 2022 18:06:33 -0400 Subject: [PATCH 09/26] Remove copy/paste comment that didn't fit. --- tnc/codec2.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tnc/codec2.py b/tnc/codec2.py index fafb817e..d1c10af4 100644 --- a/tnc/codec2.py +++ b/tnc/codec2.py @@ -238,10 +238,7 @@ class MODEMSTATS(ctypes.Structure): ("uw_fails", ctypes.c_int), ("neyetr", ctypes.c_int), # How many eye traces are plotted ("neyesamp", ctypes.c_int), # How many samples in the eye diagram - ( - "f_est", - (ctypes.c_float * MODEM_STATS_MAX_F_EST), - ), # How many samples in the eye diagram + ("f_est", (ctypes.c_float * MODEM_STATS_MAX_F_EST)), ("fft_buf", (ctypes.c_float * MODEM_STATS_NSPEC * 2)), ] From 87ebea6c52bd46d5e762856dea46b437c2691bbd Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Mon, 23 May 2022 21:22:54 -0400 Subject: [PATCH 10/26] Remove annoying debug message. --- tnc/modem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tnc/modem.py b/tnc/modem.py index a572799e..1e2cb1c7 100644 --- a/tnc/modem.py +++ b/tnc/modem.py @@ -397,7 +397,7 @@ class RF: status: """ - structlog.get_logger("structlog").debug("[MDM] callback") + # structlog.get_logger("structlog").debug("[MDM] callback") x = np.frombuffer(data_in48k, dtype=np.int16) x = self.resampler.resample48_to_8(x) From 4c16efaf2c29606c152df59e89e022d0c1820001 Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Tue, 24 May 2022 18:28:06 -0400 Subject: [PATCH 11/26] Add retries to test_tnc to make passing more likely. --- test/test_tnc.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/test/test_tnc.py b/test/test_tnc.py index 44bc04fa..d34a55b1 100755 --- a/test/test_tnc.py +++ b/test/test_tnc.py @@ -7,6 +7,7 @@ import sys import time import pytest +import structlog # pylint: disable=wrong-import-position sys.path.insert(0, "..") @@ -21,19 +22,28 @@ import test_tnc_ISS as iss @pytest.mark.parametrize("command", ["CQ", "PING", "BEACON"]) def test_tnc(command): - iss_proc = multiprocessing.Process(target=iss.t_arq_iss, args=[command]) - irs_proc = multiprocessing.Process(target=irs.t_arq_irs, args=[command]) - # print("Starting threads.") - iss_proc.start() - irs_proc.start() + # This test is currently a little inconsistent. + iss_proc: multiprocessing.Process = None + irs_proc: multiprocessing.Process = None + for _ in range(3): + iss_proc = multiprocessing.Process(target=iss.t_arq_iss, args=[command]) + irs_proc = multiprocessing.Process(target=irs.t_arq_irs, args=[command]) + # print("Starting threads.") + iss_proc.start() + irs_proc.start() - time.sleep(12) + time.sleep(12) - # print("Terminating threads.") - irs_proc.terminate() - iss_proc.terminate() - irs_proc.join() - iss_proc.join() + # print("Terminating threads.") + irs_proc.terminate() + iss_proc.terminate() + irs_proc.join() + iss_proc.join() + + if iss_proc.exitcode == 0 and irs_proc.exitcode == 0: + break + + structlog.get_logger(__name__).error("Retrying.") for idx in range(2): try: From 98c1030c249fae78a00b07efb54093a37e9178e5 Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Wed, 25 May 2022 21:23:30 -0400 Subject: [PATCH 12/26] Incorporate some changes from pep8_improvements. Simplify structlog calls. Other refactoring. --- tnc/audio.py | 40 ++-- tnc/codec2.py | 21 +-- tnc/daemon.py | 255 ++++++++++++++----------- tnc/data_handler.py | 40 ++-- tnc/helpers.py | 248 +++++++++++++++---------- tnc/log_handler.py | 17 +- tnc/main.py | 253 ++++++++++++++++++++----- tnc/modem.py | 67 +++---- tnc/rig.py | 180 +++++++++++------- tnc/rigctl.py | 121 +++++++----- tnc/rigctld.py | 90 +++++---- tnc/rigdummy.py | 5 +- tnc/sock.py | 444 +++++++++++++++++++++++++++----------------- tnc/static.py | 57 +++--- 14 files changed, 1140 insertions(+), 698 deletions(-) diff --git a/tnc/audio.py b/tnc/audio.py index 23c438dd..5ec339f8 100644 --- a/tnc/audio.py +++ b/tnc/audio.py @@ -1,4 +1,3 @@ - import atexit import json import multiprocessing @@ -8,15 +7,16 @@ import sounddevice as sd atexit.register(sd._terminate) + def get_audio_devices(): """ return list of input and output audio devices in own process to avoid crashes of portaudio on raspberry pi also uses a process data manager """ - # we need to run this on windows for multiprocessing support + # we need to run this on Windows for multiprocessing support # multiprocessing.freeze_support() - #multiprocessing.get_context('spawn') + # multiprocessing.get_context("spawn") # we need to reset and initialize sounddevice before running the multiprocessing part. # If we are not doing this at this early point, not all devices will be displayed @@ -26,41 +26,43 @@ def get_audio_devices(): with multiprocessing.Manager() as manager: proxy_input_devices = manager.list() proxy_output_devices = manager.list() - #print(multiprocessing.get_start_method()) - p = multiprocessing.Process(target=fetch_audio_devices, args=(proxy_input_devices, proxy_output_devices)) - p.start() - p.join() + # print(multiprocessing.get_start_method()) + proc = multiprocessing.Process( + target=fetch_audio_devices, args=(proxy_input_devices, proxy_output_devices) + ) + proc.start() + proc.join() return list(proxy_input_devices), list(proxy_output_devices) + def fetch_audio_devices(input_devices, output_devices): """ get audio devices from portaudio Args: input_devices: proxy variable for input devices - output_devices: proxy variable for outout devices + output_devices: proxy variable for output devices Returns: """ devices = sd.query_devices(device=None, kind=None) for index, device in enumerate(devices): - #for i in range(0, p.get_device_count()): - # we need to do a try exception, beacuse for windows theres no audio device range + # Use a try/except block beacuse Windows doesn't have an audio device range try: name = device["name"] - maxOutputChannels = device["max_output_channels"] - maxInputChannels = device["max_input_channels"] + max_output_channels = device["max_output_channels"] + max_input_channels = device["max_input_channels"] - except Exception as e: - print(e) - maxInputChannels = 0 - maxOutputChannels = 0 - name = '' + except Exception as err: + print(err) + max_input_channels = 0 + max_output_channels = 0 + name = "" - if maxInputChannels > 0: + if max_input_channels > 0: input_devices.append({"id": index, "name": name}) - if maxOutputChannels > 0: + if max_output_channels > 0: output_devices.append({"id": index, "name": name}) diff --git a/tnc/codec2.py b/tnc/codec2.py index d1c10af4..be458ff6 100644 --- a/tnc/codec2.py +++ b/tnc/codec2.py @@ -16,6 +16,7 @@ from threading import Lock import numpy as np import structlog +log = structlog.get_logger(__file__) # Enum for codec2 modes class FREEDV_MODE(Enum): @@ -65,7 +66,7 @@ if hasattr(sys, "_MEIPASS"): else: sys.path.append(os.path.abspath(".")) -structlog.get_logger("structlog").info("[C2 ] Searching for libcodec2...") +log.info("[C2 ] Searching for libcodec2...") if sys.platform == "linux": files = glob.glob(r"**/*libcodec2*", recursive=True) files.append("libcodec2.so") @@ -80,16 +81,14 @@ api = None for file in files: try: api = ctypes.CDLL(file) - structlog.get_logger("structlog").info("[C2 ] Libcodec2 loaded", path=file) + log.info("[C2 ] Libcodec2 loaded", path=file) break - except OSError as e: - structlog.get_logger("structlog").warning( - "[C2 ] Libcodec2 found but not loaded", path=file, e=e - ) + except OSError as err: + log.warning("[C2 ] Libcodec2 found but not loaded", path=file, e=err) # Quit module if codec2 cant be loaded if api is None or "api" not in locals(): - structlog.get_logger("structlog").critical("[C2 ] Libcodec2 not loaded") + log.critical("[C2 ] Libcodec2 not loaded - Exiting") sys.exit(1) # ctypes function init @@ -271,7 +270,7 @@ api.rx_sync_flags_to_text = [ # type: ignore # Audio buffer --------------------------------------------------------- class audio_buffer: """ - Thread safe audio buffer, which fits to needs of codec2 + Thread-safe audio buffer, which fits the needs of codec2 made by David Rowe, VK5DGR """ @@ -279,9 +278,7 @@ class audio_buffer: # A buffer of int16 samples, using a fixed length numpy array self.buffer for storage # self.nbuffer is the current number of samples in the buffer def __init__(self, size): - structlog.get_logger("structlog").debug( - "[C2 ] Creating audio buffer", size=size - ) + log.debug("[C2 ] Creating audio buffer", size=size) self.size = size self.buffer = np.zeros(size, dtype=np.int16) self.nbuffer = 0 @@ -343,7 +340,7 @@ class resampler: MEM48 = api.FDMDV_OS_TAPS_48K def __init__(self): - structlog.get_logger("structlog").debug("[C2 ] Create 48<->8 kHz resampler") + log.debug("[C2 ] Create 48<->8 kHz resampler") self.filter_mem8 = np.zeros(self.MEM8, dtype=np.int16) self.filter_mem48 = np.zeros(self.MEM48) diff --git a/tnc/daemon.py b/tnc/daemon.py index 9b7c166d..4297f7a8 100755 --- a/tnc/daemon.py +++ b/tnc/daemon.py @@ -15,8 +15,6 @@ import argparse import atexit import multiprocessing import os -import queue -import re import signal import socketserver import subprocess @@ -24,17 +22,14 @@ import sys import threading import time -import crcengine -import psutil -import serial.tools.list_ports -import structlog -import ujson as json - import audio -import helpers +import crcengine import log_handler +import serial.tools.list_ports import sock import static +import structlog +import ujson as json # signal handler for closing aplication @@ -47,26 +42,35 @@ def signal_handler(sig, frame): Returns: system exit """ - print('Closing daemon...') + print("Closing daemon...") sock.CLOSE_SIGNAL = True sys.exit(0) + signal.signal(signal.SIGINT, signal_handler) -class DAEMON(): + +class DAEMON: """ Daemon class """ + + log = structlog.get_logger(__name__) + def __init__(self): # load crc engine - self.crc_algorithm = crcengine.new('crc16-ccitt-false') # load crc8 library + self.crc_algorithm = crcengine.new("crc16-ccitt-false") # load crc8 library self.daemon_queue = sock.DAEMON_QUEUE - update_audio_devices = threading.Thread(target=self.update_audio_devices, name="UPDATE_AUDIO_DEVICES", daemon=True) + update_audio_devices = threading.Thread( + target=self.update_audio_devices, name="UPDATE_AUDIO_DEVICES", daemon=True + ) update_audio_devices.start() - update_serial_devices = threading.Thread(target=self.update_serial_devices, name="UPDATE_SERIAL_DEVICES", daemon=True) + update_serial_devices = threading.Thread( + target=self.update_serial_devices, name="UPDATE_SERIAL_DEVICES", daemon=True + ) update_serial_devices.start() worker = threading.Thread(target=self.worker, name="WORKER", daemon=True) @@ -79,9 +83,15 @@ class DAEMON(): while 1: try: if not static.TNCSTARTED: - static.AUDIO_INPUT_DEVICES, static.AUDIO_OUTPUT_DEVICES = audio.get_audio_devices() - except Exception as e: - structlog.get_logger("structlog").error("[DMN] update_audio_devices: Exception gathering audio devices:", e=e) + ( + static.AUDIO_INPUT_DEVICES, + static.AUDIO_OUTPUT_DEVICES, + ) = audio.get_audio_devices() + except Exception as err1: + self.log.error( + "[DMN] update_audio_devices: Exception gathering audio devices:", + e=err1, + ) # print(e) time.sleep(1) @@ -91,21 +101,26 @@ class DAEMON(): """ while 1: try: - #print("update serial") + # print("update serial") serial_devices = [] ports = serial.tools.list_ports.comports() for port, desc, hwid in ports: # calculate hex of hwid if we have unique names - crc_hwid = self.crc_algorithm(bytes(hwid, encoding='utf-8')) - crc_hwid = crc_hwid.to_bytes(2, byteorder='big') + crc_hwid = self.crc_algorithm(bytes(hwid, encoding="utf-8")) + crc_hwid = crc_hwid.to_bytes(2, byteorder="big") crc_hwid = crc_hwid.hex() description = f"{desc} [{crc_hwid}]" - serial_devices.append({"port": str(port), "description": str(description) }) + serial_devices.append( + {"port": str(port), "description": str(description)} + ) static.SERIAL_DEVICES = serial_devices time.sleep(1) - except Exception as e: - structlog.get_logger("structlog").error("[DMN] update_serial_devices: Exception gathering serial devices:", e=e) + except Exception as err1: + self.log.error( + "[DMN] update_serial_devices: Exception gathering serial devices:", + e=err1, + ) # print(e) def worker(self): @@ -140,131 +155,131 @@ class DAEMON(): # data[22] tx-audio-level # data[23] respond_to_cq - if data[0] == 'STARTTNC': - structlog.get_logger("structlog").warning("[DMN] Starting TNC", rig=data[5], port=data[6]) + if data[0] == "STARTTNC": + self.log.warning("[DMN] Starting TNC", rig=data[5], port=data[6]) # list of parameters, necessary for running subprocess command as a list options = [] - options.append('--port') + options.append("--port") options.append(str(static.DAEMONPORT - 1)) - options.append('--mycall') + options.append("--mycall") options.append(data[1]) - options.append('--mygrid') + options.append("--mygrid") options.append(data[2]) - options.append('--rx') + options.append("--rx") options.append(data[3]) - options.append('--tx') + options.append("--tx") options.append(data[4]) # if radiocontrol != disabled # this should hopefully avoid a ton of problems if we are just running in # disabled mode - if data[13] != 'disabled': - options.append('--devicename') + if data[13] != "disabled": + options.append("--devicename") options.append(data[5]) - options.append('--deviceport') + options.append("--deviceport") options.append(data[6]) - options.append('--serialspeed') + options.append("--serialspeed") options.append(data[7]) - options.append('--pttprotocol') + options.append("--pttprotocol") options.append(data[8]) - options.append('--pttport') + options.append("--pttport") options.append(data[9]) - options.append('--data_bits') + options.append("--data_bits") options.append(data[10]) - options.append('--stop_bits') + options.append("--stop_bits") options.append(data[11]) - options.append('--handshake') + options.append("--handshake") options.append(data[12]) - options.append('--radiocontrol') + options.append("--radiocontrol") options.append(data[13]) - if data[13] == 'rigctld': - options.append('--rigctld_ip') + if data[13] == "rigctld": + options.append("--rigctld_ip") options.append(data[14]) - options.append('--rigctld_port') + options.append("--rigctld_port") options.append(data[15]) - if data[16] == 'True': - options.append('--scatter') + if data[16] == "True": + options.append("--scatter") - if data[17] == 'True': - options.append('--fft') + if data[17] == "True": + options.append("--fft") - if data[18] == 'True': - options.append('--500hz') + if data[18] == "True": + options.append("--500hz") - options.append('--tuning_range_fmin') + options.append("--tuning_range_fmin") options.append(data[19]) - options.append('--tuning_range_fmax') + options.append("--tuning_range_fmax") options.append(data[20]) # overriding FSK mode - #if data[21] == 'True': - # options.append('--fsk') + # if data[21] == "True": + # options.append("--fsk") - options.append('--tx-audio-level') + options.append("--tx-audio-level") options.append(data[22]) - if data[23] == 'True': - options.append('--qrv') + if data[23] == "True": + options.append("--qrv") # Try running tnc from binary, else run from source # This helps running the tnc in a developer environment try: command = [] - if sys.platform in ['linux', 'darwin']: - command.append('./freedata-tnc') - elif sys.platform in ['win32', 'win64']: - command.append('freedata-tnc.exe') + if sys.platform in ["linux", "darwin"]: + command.append("./freedata-tnc") + elif sys.platform in ["win32", "win64"]: + command.append("freedata-tnc.exe") command += options p = subprocess.Popen(command) atexit.register(p.kill) - structlog.get_logger("structlog").info("[DMN] TNC started", path="binary") - except FileNotFoundError as e: - structlog.get_logger("structlog").error("[DMN] worker: Exception:", e=e) + self.log.info("[DMN] TNC started", path="binary") + except FileNotFoundError as err1: + self.log.info("[DMN] worker: ", e=err1) command = [] - if sys.platform in ['linux', 'darwin']: - command.append('python3') - elif sys.platform in ['win32', 'win64']: - command.append('python') + if sys.platform in ["linux", "darwin"]: + command.append("python3") + elif sys.platform in ["win32", "win64"]: + command.append("python") - command.append('main.py') + command.append("main.py") command += options p = subprocess.Popen(command) atexit.register(p.kill) - structlog.get_logger("structlog").info("[DMN] TNC started", path="source") + self.log.info("[DMN] TNC started", path="source") static.TNCPROCESS = p # .pid static.TNCSTARTED = True - ''' + """ # WE HAVE THIS PART in SOCKET - if data[0] == 'STOPTNC': + if data[0] == "STOPTNC": static.TNCPROCESS.kill() - structlog.get_logger("structlog").warning("[DMN] Stopping TNC") + self.log.warning("[DMN] Stopping TNC") #os.kill(static.TNCPROCESS, signal.SIGKILL) static.TNCSTARTED = False - ''' + """ # data[1] devicename # data[2] deviceport # data[3] serialspeed @@ -276,7 +291,7 @@ class DAEMON(): # data[9] radiocontrol # data[10] rigctld_ip # data[11] rigctld_port - if data[0] == 'TEST_HAMLIB': + if data[0] == "TEST_HAMLIB": devicename = data[1] deviceport = data[2] serialspeed = data[3] @@ -290,19 +305,28 @@ class DAEMON(): rigctld_port = data[11] # check how we want to control the radio - if radiocontrol == 'direct': + if radiocontrol == "direct": import rig - elif radiocontrol == 'rigctl': + elif radiocontrol == "rigctl": import rigctl as rig - elif radiocontrol == 'rigctld': + elif radiocontrol == "rigctld": import rigctld as rig else: import rigdummy as rig hamlib = rig.radio() - hamlib.open_rig(devicename=devicename, deviceport=deviceport, hamlib_ptt_type=pttprotocol, - serialspeed=serialspeed, pttport=pttport, data_bits=data_bits, stop_bits=stop_bits, - handshake=handshake, rigctld_ip=rigctld_ip, rigctld_port = rigctld_port) + hamlib.open_rig( + devicename=devicename, + deviceport=deviceport, + hamlib_ptt_type=pttprotocol, + serialspeed=serialspeed, + pttport=pttport, + data_bits=data_bits, + stop_bits=stop_bits, + handshake=handshake, + rigctld_ip=rigctld_ip, + rigctld_port=rigctld_port, + ) hamlib_version = rig.hamlib_version @@ -310,14 +334,14 @@ class DAEMON(): pttstate = hamlib.get_ptt() if pttstate: - structlog.get_logger("structlog").info("[DMN] Hamlib PTT", status='SUCCESS') - response = {'command': 'test_hamlib', 'result': 'SUCCESS'} + self.log.info("[DMN] Hamlib PTT", status="SUCCESS") + response = {"command": "test_hamlib", "result": "SUCCESS"} elif not pttstate: - structlog.get_logger("structlog").warning("[DMN] Hamlib PTT", status='NO SUCCESS') - response = {'command': 'test_hamlib', 'result': 'NOSUCCESS'} + self.log.warning("[DMN] Hamlib PTT", status="NO SUCCESS") + response = {"command": "test_hamlib", "result": "NOSUCCESS"} else: - structlog.get_logger("structlog").error("[DMN] Hamlib PTT", status='FAILED') - response = {'command': 'test_hamlib', 'result': 'FAILED'} + self.log.error("[DMN] Hamlib PTT", status="FAILED") + response = {"command": "test_hamlib", "result": "FAILED"} hamlib.set_ptt(False) hamlib.close_rig() @@ -325,51 +349,74 @@ class DAEMON(): jsondata = json.dumps(response) sock.SOCKET_QUEUE.put(jsondata) - except Exception as e: - structlog.get_logger("structlog").error("[DMN] worker: Exception: ", e=e) + except Exception as err1: + self.log.error("[DMN] worker: Exception: ", e=err1) # print(e) -if __name__ == '__main__': + +if __name__ == "__main__": + mainlog = structlog.get_logger(__file__) # we need to run this on windows for multiprocessing support multiprocessing.freeze_support() # --------------------------------------------GET PARAMETER INPUTS - PARSER = argparse.ArgumentParser(description='FreeDATA Daemon') - PARSER.add_argument('--port', dest="socket_port", default=3001, help="Socket port in the range of 1024-65536", type=int) + PARSER = argparse.ArgumentParser(description="FreeDATA Daemon") + PARSER.add_argument( + "--port", + dest="socket_port", + default=3001, + help="Socket port in the range of 1024-65536", + type=int, + ) ARGS = PARSER.parse_args() static.DAEMONPORT = ARGS.socket_port try: - if sys.platform == 'linux': - logging_path = os.getenv("HOME") + '/.config/' + 'FreeDATA/' + 'daemon' + if sys.platform == "linux": + logging_path = os.getenv("HOME") + "/.config/" + "FreeDATA/" + "daemon" - if sys.platform == 'darwin': - logging_path = os.getenv("HOME") + '/Library/' + 'Application Support/' + 'FreeDATA/' + 'daemon' + if sys.platform == "darwin": + logging_path = ( + os.getenv("HOME") + + "/Library/" + + "Application Support/" + + "FreeDATA/" + + "daemon" + ) - if sys.platform in ['win32', 'win64']: - logging_path = os.getenv('APPDATA') + '/' + 'FreeDATA/' + 'daemon' + if sys.platform in ["win32", "win64"]: + logging_path = os.getenv("APPDATA") + "/" + "FreeDATA/" + "daemon" if not os.path.exists(logging_path): os.makedirs(logging_path) log_handler.setup_logging(logging_path) - except Exception as e: - structlog.get_logger("structlog").error("[DMN] logger init error", exception=e) + except Exception as err: + mainlog.error("[DMN] logger init error", exception=err) try: - structlog.get_logger("structlog").info("[DMN] Starting TCP/IP socket", port=static.DAEMONPORT) + mainlog.info("[DMN] Starting TCP/IP socket", port=static.DAEMONPORT) # https://stackoverflow.com/a/16641793 socketserver.TCPServer.allow_reuse_address = True - cmdserver = sock.ThreadedTCPServer((static.HOST, static.DAEMONPORT), sock.ThreadedTCPRequestHandler) + cmdserver = sock.ThreadedTCPServer( + (static.HOST, static.DAEMONPORT), sock.ThreadedTCPRequestHandler + ) server_thread = threading.Thread(target=cmdserver.serve_forever) server_thread.daemon = True server_thread.start() - except Exception as e: - structlog.get_logger("structlog").error("[DMN] Starting TCP/IP socket failed", port=static.DAEMONPORT, e=e) + except Exception as err: + mainlog.error( + "[DMN] Starting TCP/IP socket failed", port=static.DAEMONPORT, e=err + ) sys.exit(1) daemon = DAEMON() - structlog.get_logger("structlog").info("[DMN] Starting FreeDATA Daemon", author="DJ2LS", year="2022", version=static.VERSION) + mainlog.info( + "[DMN] Starting FreeDATA Daemon", + author="DJ2LS", + year="2022", + version=static.VERSION, + ) while True: time.sleep(1) diff --git a/tnc/data_handler.py b/tnc/data_handler.py index d5e00e33..875094b7 100644 --- a/tnc/data_handler.py +++ b/tnc/data_handler.py @@ -81,7 +81,7 @@ class DATA: self.data_channel_max_retries = 5 self.datachannel_timeout = False - # List of codec2 modes to use in 'low bandwidth' mode. + # List of codec2 modes to use in "low bandwidth" mode. self.mode_list_low_bw = [ codec2.FREEDV_MODE.datac0.value, codec2.FREEDV_MODE.datac3.value, @@ -89,7 +89,7 @@ class DATA: # List for time to wait for corresponding mode in seconds self.time_list_low_bw = [3, 7] - # List of codec2 modes to use in 'high bandwidth' mode. + # List of codec2 modes to use in "high bandwidth" mode. self.mode_list_high_bw = [ codec2.FREEDV_MODE.datac0.value, codec2.FREEDV_MODE.datac3.value, @@ -101,13 +101,13 @@ class DATA: # Mode list for selecting between low bandwidth ( 500Hz ) and modes with higher bandwidth # but ability to fall back to low bandwidth modes if needed. if static.LOW_BANDWITH_MODE: - # List of codec2 modes to use in 'low bandwidth' mode. + # List of codec2 modes to use in "low bandwidth" mode. self.mode_list = self.mode_list_low_bw # list of times to wait for corresponding mode in seconds self.time_list = self.time_list_low_bw else: - # List of codec2 modes to use in 'high bandwidth' mode. + # List of codec2 modes to use in "high bandwidth" mode. self.mode_list = self.mode_list_high_bw # list of times to wait for corresponding mode in seconds self.time_list = self.time_list_high_bw @@ -968,7 +968,7 @@ class DATA: break # break retry loop # We need this part for leaving the repeat loop - # static.ARQ_STATE == 'DATA' --> when stopping transmission manually + # static.ARQ_STATE == "DATA" --> when stopping transmission manually if not static.ARQ_STATE: # print("not ready for data...leaving loop....") break @@ -1205,10 +1205,7 @@ class DATA: # ############################################################################################################ def arq_session_handler(self) -> bool: """ - Create a session with `callsign` and wait until the session is open. - - Args: - callsign: + Create a session with `static.DXCALLSIGN` and wait until the session is open. Returns: True if the session was opened successfully @@ -1233,7 +1230,7 @@ class DATA: static.ARQ_SESSION_STATE = "connecting" if static.ARQ_SESSION and static.ARQ_SESSION_STATE == "connected": - # static.ARQ_SESSION_STATE = 'connected' + # static.ARQ_SESSION_STATE = "connected" return True static.ARQ_SESSION_STATE = "failed" @@ -1278,7 +1275,6 @@ class DATA: time.sleep(0.01) # Stop waiting if data channel is opened if static.ARQ_SESSION: - # eventuell einfach nur return true um die nächste break ebene zu vermeiden? return True # Session connect timeout, send close_session frame to @@ -1392,8 +1388,8 @@ class DATA: def transmit_session_heartbeat(self) -> None: """Send ARQ sesion heartbeat while connected""" # static.ARQ_SESSION = True - # static.TNC_STATE = 'BUSY' - # static.ARQ_SESSION_STATE = 'connected' + # static.TNC_STATE = "BUSY" + # static.ARQ_SESSION_STATE = "connected" connection_frame = bytearray(14) connection_frame[:1] = bytes([222]) @@ -1933,9 +1929,7 @@ class DATA: if static.ENABLE_FSK: self.enqueue_frame_for_tx( beacon_frame, - c2_mode=codec2.freedv_get_mode_value_by_name( - "FSK_LDPC_0" - ), + c2_mode=codec2.FREEDV_MODE.fsk_ldpc_0.value, ) else: self.enqueue_frame_for_tx(beacon_frame) @@ -2010,7 +2004,7 @@ class DATA: if static.ENABLE_FSK: self.enqueue_frame_for_tx( - cq_frame, c2_mode=codec2.freedv_get_mode_value_by_name("FSK_LDPC_0") + cq_frame, c2_mode=codec2.FREEDV_MODE.fsk_ldpc_0.value ) else: self.enqueue_frame_for_tx(cq_frame) @@ -2074,7 +2068,7 @@ class DATA: if static.ENABLE_FSK: self.enqueue_frame_for_tx( - qrv_frame, c2_mode=codec2.freedv_get_mode_value_by_name("FSK_LDPC_0") + qrv_frame, c2_mode=codec2.FREEDV_MODE.fsk_ldpc_0.value ) else: self.enqueue_frame_for_tx(qrv_frame) @@ -2299,18 +2293,16 @@ class DATA: """ # set modes we want to listen to - mode_name = codec2.freedv_get_mode_name_by_value(mode) - - if mode_name == "datac1": + if mode == codec2.FREEDV_MODE.datac1.value: modem.RECEIVE_DATAC1 = True self.log.debug("[TNC] Changing listening data mode", mode="datac1") - elif mode_name == "datac3": + elif mode == codec2.FREEDV_MODE.datac3.value: modem.RECEIVE_DATAC3 = True self.log.debug("[TNC] Changing listening data mode", mode="datac3") - elif mode_name == "fsk_ldpc_1": + elif mode == codec2.FREEDV_MODE.fsk_ldpc_1.value: modem.RECEIVE_FSK_LDPC_1 = True self.log.debug("[TNC] Changing listening data mode", mode="fsk_ldpc_1") - elif mode_name == "allmodes": + elif mode == codec2.FREEDV_MODE.allmodes.value: modem.RECEIVE_DATAC1 = True modem.RECEIVE_DATAC3 = True modem.RECEIVE_FSK_LDPC_1 = True diff --git a/tnc/helpers.py b/tnc/helpers.py index 00378f7d..06471ab8 100644 --- a/tnc/helpers.py +++ b/tnc/helpers.py @@ -8,9 +8,10 @@ Created on Fri Dec 25 21:25:14 2020 import time import crcengine +import static import structlog -import static +log = structlog.get_logger(__file__) def wait(seconds: float) -> bool: @@ -27,6 +28,7 @@ def wait(seconds: float) -> bool: time.sleep(0.01) return True + def get_crc_8(data) -> bytes: """Author: DJ2LS @@ -40,11 +42,12 @@ def get_crc_8(data) -> bytes: Returns: CRC-8 (CCITT) of the provided data as bytes """ - crc_algorithm = crcengine.new('crc8-ccitt') # load crc8 library + crc_algorithm = crcengine.new("crc8-ccitt") # load crc8 library crc_data = crc_algorithm(data) - crc_data = crc_data.to_bytes(1, byteorder='big') + crc_data = crc_data.to_bytes(1, byteorder="big") return crc_data + def get_crc_16(data) -> bytes: """Author: DJ2LS @@ -58,11 +61,12 @@ def get_crc_16(data) -> bytes: Returns: CRC-16 (CCITT) of the provided data as bytes """ - crc_algorithm = crcengine.new('crc16-ccitt-false') # load crc16 library + crc_algorithm = crcengine.new("crc16-ccitt-false") # load crc16 library crc_data = crc_algorithm(data) - crc_data = crc_data.to_bytes(2, byteorder='big') + crc_data = crc_data.to_bytes(2, byteorder="big") return crc_data + def get_crc_24(data) -> bytes: """Author: DJ2LS @@ -77,13 +81,20 @@ def get_crc_24(data) -> bytes: Returns: CRC-24 (OpenPGP) of the provided data as bytes """ - crc_algorithm = crcengine.create(0x864cfb, 24, 0xb704ce, ref_in=False, - ref_out=False, xor_out=0, - name='crc-24-openpgp') + crc_algorithm = crcengine.create( + 0x864CFB, + 24, + 0xB704CE, + ref_in=False, + ref_out=False, + xor_out=0, + name="crc-24-openpgp", + ) crc_data = crc_algorithm(data) - crc_data = crc_data.to_bytes(3, byteorder='big') + crc_data = crc_data.to_bytes(3, byteorder="big") return crc_data + def get_crc_32(data: bytes) -> bytes: """Author: DJ2LS @@ -97,11 +108,12 @@ def get_crc_32(data: bytes) -> bytes: Returns: CRC-32 of the provided data as bytes """ - crc_algorithm = crcengine.new('crc32') # load crc32 library + crc_algorithm = crcengine.new("crc32") # load crc32 library crc_data = crc_algorithm(data) - crc_data = crc_data.to_bytes(4, byteorder='big') + crc_data = crc_data.to_bytes(4, byteorder="big") return crc_data + def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency): """ @@ -118,24 +130,46 @@ def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency): """ # check if buffer empty if len(static.HEARD_STATIONS) == 0: - static.HEARD_STATIONS.append([dxcallsign, dxgrid, int(time.time()), datatype, snr, offset, frequency]) + static.HEARD_STATIONS.append( + [dxcallsign, dxgrid, int(time.time()), datatype, snr, offset, frequency] + ) # if not, we search and update else: for i in range(len(static.HEARD_STATIONS)): # Update callsign with new timestamp if static.HEARD_STATIONS[i].count(dxcallsign) > 0: - static.HEARD_STATIONS[i] = [dxcallsign, dxgrid, int(time.time()), datatype, snr, offset, frequency] + static.HEARD_STATIONS[i] = [ + dxcallsign, + dxgrid, + int(time.time()), + datatype, + snr, + offset, + frequency, + ] break # Insert if nothing found if i == len(static.HEARD_STATIONS) - 1: - static.HEARD_STATIONS.append([dxcallsign, dxgrid, int(time.time()), datatype, snr, offset, frequency]) + static.HEARD_STATIONS.append( + [ + dxcallsign, + dxgrid, + int(time.time()), + datatype, + snr, + offset, + frequency, + ] + ) break + # for idx, item in enumerate(static.HEARD_STATIONS): # if dxcallsign in item: # item = [dxcallsign, int(time.time())] # static.HEARD_STATIONS[idx] = item + def callsign_to_bytes(callsign) -> bytes: """ @@ -146,48 +180,49 @@ def callsign_to_bytes(callsign) -> bytes: """ # http://www.aprs.org/aprs11/SSIDs.txt - #-0 Your primary station usually fixed and message capable - #-1 generic additional station, digi, mobile, wx, etc - #-2 generic additional station, digi, mobile, wx, etc - #-3 generic additional station, digi, mobile, wx, etc - #-4 generic additional station, digi, mobile, wx, etc - #-5 Other networks (Dstar, Iphones, Androids, Blackberry's etc) - #-6 Special activity, Satellite ops, camping or 6 meters, etc - #-7 walkie talkies, HT's or other human portable - #-8 boats, sailboats, RV's or second main mobile - #-9 Primary Mobile (usually message capable) - #-10 internet, Igates, echolink, winlink, AVRS, APRN, etc - #-11 balloons, aircraft, spacecraft, etc - #-12 APRStt, DTMF, RFID, devices, one-way trackers*, etc - #-13 Weather stations - #-14 Truckers or generally full time drivers - #-15 generic additional station, digi, mobile, wx, etc + # -0 Your primary station usually fixed and message capable + # -1 generic additional station, digi, mobile, wx, etc + # -2 generic additional station, digi, mobile, wx, etc + # -3 generic additional station, digi, mobile, wx, etc + # -4 generic additional station, digi, mobile, wx, etc + # -5 Other networks (Dstar, Iphones, Androids, Blackberry's etc) + # -6 Special activity, Satellite ops, camping or 6 meters, etc + # -7 walkie talkies, HT's or other human portable + # -8 boats, sailboats, RV's or second main mobile + # -9 Primary Mobile (usually message capable) + # -10 internet, Igates, echolink, winlink, AVRS, APRN, etc + # -11 balloons, aircraft, spacecraft, etc + # -12 APRStt, DTMF, RFID, devices, one-way trackers*, etc + # -13 Weather stations + # -14 Truckers or generally full time drivers + # -15 generic additional station, digi, mobile, wx, etc # Try converting to bytestring if possible type string try: - callsign = bytes(callsign, 'utf-8') - except TypeError as e: - structlog.get_logger("structlog").debug("[HLP] callsign_to_bytes: Exception converting callsign to bytes:", e=e) - pass + callsign = bytes(callsign, "utf-8") + except TypeError as err: + log.debug("[HLP] callsign_to_bytes: Error converting callsign to bytes:", e=err) - # Need this step to reduce the needed payload by the callsign (stripping "-" out of the callsign) - callsign = callsign.split(b'-') + # Need this step to reduce the needed payload by the callsign + # (stripping "-" out of the callsign) + callsign = callsign.split(b"-") ssid = 0 try: ssid = int(callsign[1]) - except IndexError as e: - structlog.get_logger("structlog").debug("[HLP] callsign_to_bytes: Error callsign SSID to integer:", e=e) + except IndexError as err: + log.debug("[HLP] callsign_to_bytes: Error callsign SSID to integer:", e=err) - #callsign = callsign[0] - #bytestring = bytearray(8) - #bytestring[:len(callsign)] = callsign - #bytestring[7:8] = bytes([ssid]) + # callsign = callsign[0] + # bytestring = bytearray(8) + # bytestring[:len(callsign)] = callsign + # bytestring[7:8] = bytes([ssid]) # ---- callsign with encoding always 6 bytes long callsign = callsign[0].decode("utf-8") ssid = bytes([ssid]).decode("utf-8") return encode_call(callsign + ssid) - #return bytes(bytestring) + # return bytes(bytestring) + def bytes_to_callsign(bytestring: bytes) -> bytes: """ @@ -200,25 +235,25 @@ def bytes_to_callsign(bytestring: bytes) -> bytes: bytes """ # http://www.aprs.org/aprs11/SSIDs.txt - #-0 Your primary station usually fixed and message capable - #-1 generic additional station, digi, mobile, wx, etc - #-2 generic additional station, digi, mobile, wx, etc - #-3 generic additional station, digi, mobile, wx, etc - #-4 generic additional station, digi, mobile, wx, etc - #-5 Other networks (Dstar, Iphones, Androids, Blackberry's etc) - #-6 Special activity, Satellite ops, camping or 6 meters, etc - #-7 walkie talkies, HT's or other human portable - #-8 boats, sailboats, RV's or second main mobile - #-9 Primary Mobile (usually message capable) - #-10 internet, Igates, echolink, winlink, AVRS, APRN, etc - #-11 balloons, aircraft, spacecraft, etc - #-12 APRStt, DTMF, RFID, devices, one-way trackers*, etc - #-13 Weather stations - #-14 Truckers or generally full time drivers - #-15 generic additional station, digi, mobile, wx, etc + # -0 Your primary station usually fixed and message capable + # -1 generic additional station, digi, mobile, wx, etc + # -2 generic additional station, digi, mobile, wx, etc + # -3 generic additional station, digi, mobile, wx, etc + # -4 generic additional station, digi, mobile, wx, etc + # -5 Other networks (Dstar, Iphones, Androids, Blackberry's etc) + # -6 Special activity, Satellite ops, camping or 6 meters, etc + # -7 walkie talkies, HT's or other human portable + # -8 boats, sailboats, RV's or second main mobile + # -9 Primary Mobile (usually message capable) + # -10 internet, Igates, echolink, winlink, AVRS, APRN, etc + # -11 balloons, aircraft, spacecraft, etc + # -12 APRStt, DTMF, RFID, devices, one-way trackers*, etc + # -13 Weather stations + # -14 Truckers or generally full time drivers + # -15 generic additional station, digi, mobile, wx, etc # we need to do this step to reduce the needed paypload by the callsign ( stripping "-" out of the callsign ) - ''' + """ callsign = bytes(bytestring[:7]) callsign = callsign.rstrip(b'\x00') ssid = int.from_bytes(bytes(bytestring[7:8]), "big") @@ -229,15 +264,17 @@ def bytes_to_callsign(bytestring: bytes) -> bytes: callsign = callsign.encode('utf-8') return bytes(callsign) - ''' + """ decoded = decode_call(bytestring) callsign = decoded[:-1] ssid = ord(bytes(decoded[-1], "utf-8")) return bytes(f"{callsign}-{ssid}", "utf-8") -def check_callsign(callsign:bytes, crc_to_check:bytes): + +def check_callsign(callsign: bytes, crc_to_check: bytes): """ - Funktion to check a crc against a callsign to calculate the ssid by generating crc until we got it + Function to check a crc against a callsign to calculate the + ssid by generating crc until we find the correct SSID Args: callsign: Callsign which we want to check @@ -249,18 +286,18 @@ def check_callsign(callsign:bytes, crc_to_check:bytes): """ # print(callsign) - structlog.get_logger("structlog").debug("[HLP] check_callsign: Checking:", callsign=callsign) + log.debug("[HLP] check_callsign: Checking:", callsign=callsign) try: # We want the callsign without SSID - callsign = callsign.split(b'-')[0] + callsign = callsign.split(b"-")[0] - except Exception as e: - structlog.get_logger("structlog").debug("[HLP] check_callsign: Error callsign SSIG to integer:", e=e) + except Exception as err: + log.debug("[HLP] check_callsign: Error callsign SSID to integer:", e=err) for ssid in static.SSID_LIST: call_with_ssid = bytearray(callsign) - call_with_ssid.extend('-'.encode('utf-8')) - call_with_ssid.extend(str(ssid).encode('utf-8')) + call_with_ssid.extend("-".encode("utf-8")) + call_with_ssid.extend(str(ssid).encode("utf-8")) callsign_crc = get_crc_24(call_with_ssid) @@ -270,6 +307,7 @@ def check_callsign(callsign:bytes, crc_to_check:bytes): return [False, ""] + def encode_grid(grid): """ @auther: DB1UJ @@ -280,30 +318,31 @@ def encode_grid(grid): """ out_code_word = 0 - grid = grid.upper() # upper case to be save + grid = grid.upper() # upper case to be save - int_first = ord(grid[0]) - 65 # -65 offset for 'A' become zero, utf8 table - int_sec = ord(grid[1]) - 65 # -65 offset for 'A' become zero, utf8 table + int_first = ord(grid[0]) - 65 # -65 offset for 'A' become zero, utf8 table + int_sec = ord(grid[1]) - 65 # -65 offset for 'A' become zero, utf8 table - int_val = (int_first * 18) + int_sec # encode for modulo devision, 2 numbers in 1 + int_val = (int_first * 18) + int_sec # encode for modulo devision, 2 numbers in 1 - out_code_word = (int_val & 0b111111111) # only 9 bit LSB A - R * A - R is needed - out_code_word <<= 9 # shift 9 bit left having space next bits, letter A-R * A-R + out_code_word = int_val & 0b111111111 # only 9 bit LSB A - R * A - R is needed + out_code_word <<= 9 # shift 9 bit left having space next bits, letter A-R * A-R - int_val = int(grid[2:4]) # number string to number int, highest value 99 - out_code_word |= (int_val & 0b1111111) # using bit OR to add new value - out_code_word <<= 7 # shift 7 bit left having space next bits, letter A-X + int_val = int(grid[2:4]) # number string to number int, highest value 99 + out_code_word |= int_val & 0b1111111 # using bit OR to add new value + out_code_word <<= 7 # shift 7 bit left having space next bits, letter A-X - int_val = ord(grid[4]) - 65 # -65 offset for 'A' become zero, utf8 table - out_code_word |= (int_val & 0b11111) # using bit OR to add new value - out_code_word <<= 5 # shift 5 bit left having space next bits, letter A-X + int_val = ord(grid[4]) - 65 # -65 offset for 'A' become zero, utf8 table + out_code_word |= int_val & 0b11111 # using bit OR to add new value + out_code_word <<= 5 # shift 5 bit left having space next bits, letter A-X - int_val = ord(grid[5]) - 65 # -65 offset for 'A' become zero, utf8 table - out_code_word |= (int_val & 0b11111) # using bit OR to add new value + int_val = ord(grid[5]) - 65 # -65 offset for 'A' become zero, utf8 table + out_code_word |= int_val & 0b11111 # using bit OR to add new value - return out_code_word.to_bytes(length=4, byteorder='big') + return out_code_word.to_bytes(length=4, byteorder="big") -def decode_grid(b_code_word:bytes): + +def decode_grid(b_code_word: bytes): """ @auther: DB1UJ Args: @@ -311,7 +350,7 @@ def decode_grid(b_code_word:bytes): Returns: grid:str: upper case maidenhead QTH locater [A-R][A-R][0-9][0-9][A-X][A-X] """ - code_word = int.from_bytes(b_code_word, byteorder='big', signed=False) + code_word = int.from_bytes(b_code_word, byteorder="big", signed=False) grid = chr((code_word & 0b11111) + 65) code_word >>= 5 @@ -321,7 +360,7 @@ def decode_grid(b_code_word:bytes): grid = str(int(code_word & 0b1111111)) + grid if (code_word & 0b1111111) < 10: - grid = f'0{grid}' + grid = f"0{grid}" code_word >>= 9 int_val = int(code_word & 0b111111111) @@ -332,6 +371,7 @@ def decode_grid(b_code_word:bytes): return grid + def encode_call(call): """ @auther: DB1UJ @@ -339,23 +379,27 @@ def encode_call(call): call:string: ham radio call sign [A-Z,0-9], last char SSID 0-63 Returns: - 6 bytes contains 6 bits/sign encoded 8 char call sign with binary SSID (only upper letters + numbers, SSID) + 6 bytes contains 6 bits/sign encoded 8 char call sign with binary SSID + (only upper letters + numbers, SSID) """ out_code_word = 0 - call = call.upper() # upper case to be save + call = call.upper() # upper case to be save - for x in call: - int_val = ord(x) - 48 # -48 reduce bits, begin with first number utf8 table - out_code_word <<= 6 # shift left 6 bit, making space for a new char - out_code_word |= (int_val & 0b111111) # bit OR adds the new char, masked with AND 0b111111 - out_code_word >>= 6 # clean last char - out_code_word <<= 6 # make clean space - out_code_word |= (ord(call[-1]) & 0b111111) # add the SSID uncoded only 0 - 63 + for char in call: + int_val = ord(char) - 48 # -48 reduce bits, begin with first number utf8 table + out_code_word <<= 6 # shift left 6 bit, making space for a new char + out_code_word |= ( + int_val & 0b111111 + ) # bit OR adds the new char, masked with AND 0b111111 + out_code_word >>= 6 # clean last char + out_code_word <<= 6 # make clean space + out_code_word |= ord(call[-1]) & 0b111111 # add the SSID uncoded only 0 - 63 - return out_code_word.to_bytes(length=6, byteorder='big') + return out_code_word.to_bytes(length=6, byteorder="big") -def decode_call(b_code_word:bytes): + +def decode_call(b_code_word: bytes): """ @auther: DB1UJ Args: @@ -364,14 +408,14 @@ def decode_call(b_code_word:bytes): Returns: call:str: upper case ham radio call sign [A-Z,0-9] + binary SSID """ - code_word = int.from_bytes(b_code_word, byteorder='big', signed=False) - ssid = chr(code_word & 0b111111) # save the uncoded binary SSID + code_word = int.from_bytes(b_code_word, byteorder="big", signed=False) + ssid = chr(code_word & 0b111111) # save the uncoded binary SSID call = str() while code_word != 0: - call = chr((code_word & 0b111111)+48) + call + call = chr((code_word & 0b111111) + 48) + call code_word >>= 6 - call = call[:-1] + ssid # remove the last char from call and replace with SSID + call = call[:-1] + ssid # remove the last char from call and replace with SSID return call diff --git a/tnc/log_handler.py b/tnc/log_handler.py index 6d489cca..bda36caf 100644 --- a/tnc/log_handler.py +++ b/tnc/log_handler.py @@ -1,3 +1,8 @@ +import logging.config + +import structlog + + # https://www.structlog.org/en/stable/standard-library.html def setup_logging(filename): """ @@ -8,8 +13,6 @@ def setup_logging(filename): Returns: """ - import logging.config - import structlog timestamper = structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S") pre_chain = [ @@ -19,7 +22,8 @@ def setup_logging(filename): timestamper, ] - logging.config.dictConfig({ + logging.config.dictConfig( + { "version": 1, "disable_existing_loggers": False, "formatters": { @@ -43,7 +47,7 @@ def setup_logging(filename): "file": { "level": "DEBUG", "class": "logging.handlers.WatchedFileHandler", - "filename": filename + '.log', + "filename": f"{filename}.log", "formatter": "plain", }, }, @@ -53,8 +57,9 @@ def setup_logging(filename): "level": "DEBUG", "propagate": True, }, - } - }) + }, + } + ) structlog.configure( processors=[ structlog.stdlib.add_log_level, diff --git a/tnc/main.py b/tnc/main.py index 6e08dfbd..35200b82 100755 --- a/tnc/main.py +++ b/tnc/main.py @@ -16,13 +16,14 @@ import sys import threading import time -import structlog - import data_handler import helpers import log_handler import modem import static +import structlog + +log = structlog.get_logger(__file__) # signal handler for closing aplication def signal_handler(sig, frame): @@ -35,56 +36,208 @@ def signal_handler(sig, frame): Returns: system exit """ - print('Closing TNC...') + print("Closing TNC...") sock.CLOSE_SIGNAL = True sys.exit(0) + signal.signal(signal.SIGINT, signal_handler) -if __name__ == '__main__': +if __name__ == "__main__": # we need to run this on windows for multiprocessing support multiprocessing.freeze_support() # --------------------------------------------GET PARAMETER INPUTS - PARSER = argparse.ArgumentParser(description='FreeDATA TNC') - PARSER.add_argument('--mycall', dest="mycall", default="AA0AA", help="My callsign", type=str) - PARSER.add_argument('--ssid', dest="ssid_list", nargs='*', default=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15], help="SSID list we are responding to", type=str) - PARSER.add_argument('--mygrid', dest="mygrid", default="JN12AA", help="My gridsquare", type=str) - PARSER.add_argument('--rx', dest="audio_input_device", default=0, help="listening sound card", type=int) - PARSER.add_argument('--tx', dest="audio_output_device", default=0, help="transmitting sound card", type=int) - PARSER.add_argument('--port', dest="socket_port", default=3000, help="Socket port in the range of 1024-65536", type=int) - PARSER.add_argument('--deviceport', dest="hamlib_device_port", default="/dev/ttyUSB0", help="Hamlib device port", type=str) - PARSER.add_argument('--devicename', dest="hamlib_device_name", default="2028", help="Hamlib device name", type=str) - PARSER.add_argument('--serialspeed', dest="hamlib_serialspeed", choices=[1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200], default=9600, help="Serialspeed", type=int) - PARSER.add_argument('--pttprotocol', dest="hamlib_ptt_type", choices=['USB', 'RIG', 'RTS', 'DTR', 'CM108', 'MICDATA', 'PARALLEL', 'DTR-H', 'DTR-L', 'NONE'], default='USB', help="PTT Type", type=str) - PARSER.add_argument('--pttport', dest="hamlib_ptt_port", default="/dev/ttyUSB0", help="PTT Port", type=str) - PARSER.add_argument('--data_bits', dest="hamlib_data_bits", choices=[7, 8], default=8, help="Hamlib data bits", type=int) - PARSER.add_argument('--stop_bits', dest="hamlib_stop_bits", choices=[1, 2], default=1, help="Hamlib stop bits", type=int) - PARSER.add_argument('--handshake', dest="hamlib_handshake", default="None", help="Hamlib handshake", type=str) - PARSER.add_argument('--radiocontrol', dest="hamlib_radiocontrol", choices=['disabled', 'direct', 'rigctl', 'rigctld'], default="disabled", help="Set how you want to control your radio") - PARSER.add_argument('--rigctld_port', dest="rigctld_port", default=4532, type=int, help="Set rigctld port") - PARSER.add_argument('--rigctld_ip', dest="rigctld_ip", default="localhost", help="Set rigctld ip") - PARSER.add_argument('--scatter', dest="send_scatter", action="store_true", help="Send scatter information via network") - PARSER.add_argument('--fft', dest="send_fft", action="store_true", help="Send fft information via network") - PARSER.add_argument('--500hz', dest="low_bandwith_mode", action="store_true", help="Enable low bandwith mode ( 500 Hz only )") - PARSER.add_argument('--fsk', dest="enable_fsk", action="store_true", help="Enable FSK mode for ping, beacon and CQ") - PARSER.add_argument('--qrv', dest="enable_respond_to_cq", action="store_true", help="Enable sending a QRV frame if CQ received") - PARSER.add_argument('--tuning_range_fmin', dest="tuning_range_fmin", choices=[-50.0, -100.0, -150.0, -200.0, -250.0], default=-50.0, help="Tuning range fmin", type=float) - PARSER.add_argument('--tuning_range_fmax', dest="tuning_range_fmax", choices=[50.0, 100.0, 150.0, 200.0, 250.0], default=50.0, help="Tuning range fmax", type=float) - PARSER.add_argument('--tx-audio-level', dest="tx_audio_level", default=50, help="Set the tx audio level at an early stage", type=int) + PARSER = argparse.ArgumentParser(description="FreeDATA TNC") + PARSER.add_argument( + "--mycall", dest="mycall", default="AA0AA", help="My callsign", type=str + ) + PARSER.add_argument( + "--ssid", + dest="ssid_list", + nargs="*", + default=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + help="SSID list we are responding to", + type=str, + ) + PARSER.add_argument( + "--mygrid", dest="mygrid", default="JN12AA", help="My gridsquare", type=str + ) + PARSER.add_argument( + "--rx", + dest="audio_input_device", + default=0, + help="listening sound card", + type=int, + ) + PARSER.add_argument( + "--tx", + dest="audio_output_device", + default=0, + help="transmitting sound card", + type=int, + ) + PARSER.add_argument( + "--port", + dest="socket_port", + default=3000, + help="Socket port in the range of 1024-65536", + type=int, + ) + PARSER.add_argument( + "--deviceport", + dest="hamlib_device_port", + default="/dev/ttyUSB0", + help="Hamlib device port", + type=str, + ) + PARSER.add_argument( + "--devicename", + dest="hamlib_device_name", + default="2028", + help="Hamlib device name", + type=str, + ) + PARSER.add_argument( + "--serialspeed", + dest="hamlib_serialspeed", + choices=[1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200], + default=9600, + help="Serialspeed", + type=int, + ) + PARSER.add_argument( + "--pttprotocol", + dest="hamlib_ptt_type", + choices=[ + "USB", + "RIG", + "RTS", + "DTR", + "CM108", + "MICDATA", + "PARALLEL", + "DTR-H", + "DTR-L", + "NONE", + ], + default="USB", + help="PTT Type", + type=str, + ) + PARSER.add_argument( + "--pttport", + dest="hamlib_ptt_port", + default="/dev/ttyUSB0", + help="PTT Port", + type=str, + ) + PARSER.add_argument( + "--data_bits", + dest="hamlib_data_bits", + choices=[7, 8], + default=8, + help="Hamlib data bits", + type=int, + ) + PARSER.add_argument( + "--stop_bits", + dest="hamlib_stop_bits", + choices=[1, 2], + default=1, + help="Hamlib stop bits", + type=int, + ) + PARSER.add_argument( + "--handshake", + dest="hamlib_handshake", + default="None", + help="Hamlib handshake", + type=str, + ) + PARSER.add_argument( + "--radiocontrol", + dest="hamlib_radiocontrol", + choices=["disabled", "direct", "rigctl", "rigctld"], + default="disabled", + help="Set how you want to control your radio", + ) + PARSER.add_argument( + "--rigctld_port", + dest="rigctld_port", + default=4532, + type=int, + help="Set rigctld port", + ) + PARSER.add_argument( + "--rigctld_ip", dest="rigctld_ip", default="localhost", help="Set rigctld ip" + ) + PARSER.add_argument( + "--scatter", + dest="send_scatter", + action="store_true", + help="Send scatter information via network", + ) + PARSER.add_argument( + "--fft", + dest="send_fft", + action="store_true", + help="Send fft information via network", + ) + PARSER.add_argument( + "--500hz", + dest="low_bandwith_mode", + action="store_true", + help="Enable low bandwith mode ( 500 Hz only )", + ) + PARSER.add_argument( + "--fsk", + dest="enable_fsk", + action="store_true", + help="Enable FSK mode for ping, beacon and CQ", + ) + PARSER.add_argument( + "--qrv", + dest="enable_respond_to_cq", + action="store_true", + help="Enable sending a QRV frame if CQ received", + ) + PARSER.add_argument( + "--tuning_range_fmin", + dest="tuning_range_fmin", + choices=[-50.0, -100.0, -150.0, -200.0, -250.0], + default=-50.0, + help="Tuning range fmin", + type=float, + ) + PARSER.add_argument( + "--tuning_range_fmax", + dest="tuning_range_fmax", + choices=[50.0, 100.0, 150.0, 200.0, 250.0], + default=50.0, + help="Tuning range fmax", + type=float, + ) + PARSER.add_argument( + "--tx-audio-level", + dest="tx_audio_level", + default=50, + help="Set the tx audio level at an early stage", + type=int, + ) ARGS = PARSER.parse_args() # additional step for beeing sure our callsign is correctly # in case we are not getting a station ssid # then we are forcing a station ssid = 0 - mycallsign = bytes(ARGS.mycall.upper(), 'utf-8') + mycallsign = bytes(ARGS.mycall.upper(), "utf-8") mycallsign = helpers.callsign_to_bytes(mycallsign) static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign) static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN) static.SSID_LIST = ARGS.ssid_list - static.MYGRID = bytes(ARGS.mygrid, 'utf-8') + static.MYGRID = bytes(ARGS.mygrid, "utf-8") static.AUDIO_INPUT_DEVICE = ARGS.audio_input_device static.AUDIO_OUTPUT_DEVICE = ARGS.audio_output_device static.PORT = ARGS.socket_port @@ -113,22 +266,30 @@ if __name__ == '__main__': # config logging try: - if sys.platform == 'linux': - logging_path = os.getenv("HOME") + '/.config/' + 'FreeDATA/' + 'tnc' + if sys.platform == "linux": + logging_path = os.getenv("HOME") + "/.config/" + "FreeDATA/" + "tnc" - if sys.platform == 'darwin': - logging_path = os.getenv("HOME") + '/Library/' + 'Application Support/' + 'FreeDATA/' + 'tnc' + if sys.platform == "darwin": + logging_path = ( + os.getenv("HOME") + + "/Library/" + + "Application Support/" + + "FreeDATA/" + + "tnc" + ) - if sys.platform in ['win32', 'win64']: - logging_path = os.getenv('APPDATA') + '/' + 'FreeDATA/' + 'tnc' + if sys.platform in ["win32", "win64"]: + logging_path = os.getenv("APPDATA") + "/" + "FreeDATA/" + "tnc" if not os.path.exists(logging_path): os.makedirs(logging_path) log_handler.setup_logging(logging_path) - except Exception as e: - structlog.get_logger("structlog").error("[DMN] logger init error", exception=e) + except Exception as err: + log.error("[DMN] logger init error", exception=err) - structlog.get_logger("structlog").info("[TNC] Starting FreeDATA", author="DJ2LS", year="2022", version=static.VERSION) + log.info( + "[TNC] Starting FreeDATA", author="DJ2LS", year="2022", version=static.VERSION + ) # start data handler data_handler.DATA() @@ -138,17 +299,19 @@ if __name__ == '__main__': # --------------------------------------------START CMD SERVER try: - structlog.get_logger("structlog").info("[TNC] Starting TCP/IP socket", port=static.PORT) + log.info("[TNC] Starting TCP/IP socket", port=static.PORT) # https://stackoverflow.com/a/16641793 socketserver.TCPServer.allow_reuse_address = True - cmdserver = sock.ThreadedTCPServer((static.HOST, static.PORT), sock.ThreadedTCPRequestHandler) + cmdserver = sock.ThreadedTCPServer( + (static.HOST, static.PORT), sock.ThreadedTCPRequestHandler + ) server_thread = threading.Thread(target=cmdserver.serve_forever) server_thread.daemon = True server_thread.start() - except Exception as e: - structlog.get_logger("structlog").error("[TNC] Starting TCP/IP socket failed", port=static.PORT, e=e) + except Exception as err: + log.error("[TNC] Starting TCP/IP socket failed", port=static.PORT, e=err) sys.exit(1) while 1: time.sleep(1) diff --git a/tnc/modem.py b/tnc/modem.py index 1e2cb1c7..7b19e9d4 100644 --- a/tnc/modem.py +++ b/tnc/modem.py @@ -16,7 +16,6 @@ import sys import threading import time from collections import deque -from typing import Union import codec2 import data_handler @@ -45,6 +44,8 @@ RECEIVE_FSK_LDPC_1 = False class RF: """Class to encapsulate interactions between the audio device and codec2""" + log = structlog.get_logger(__name__) + def __init__(self) -> None: self.sampler_avg = 0 @@ -188,7 +189,7 @@ class RF: self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv) self.fsk_ldpc_nin_0 = codec2.api.freedv_nin(self.fsk_ldpc_freedv_0) self.fsk_ldpc_nin_1 = codec2.api.freedv_nin(self.fsk_ldpc_freedv_1) - # structlog.get_logger("structlog").debug("[MDM] RF: ",datac0_nin=self.datac0_nin) + # self.log.debug("[MDM] RF: ",datac0_nin=self.datac0_nin) # --------------------------------------------CREATE PYAUDIO INSTANCE if not TESTMODE: @@ -202,24 +203,24 @@ class RF: blocksize=4800, ) atexit.register(self.stream.stop) - structlog.get_logger("structlog").info( + self.log.info( "[MDM] init: opened audio devices" ) - except Exception as e: - structlog.get_logger("structlog").error( - "[MDM] init: can't open audio device. Exit", e=e + except Exception as err: + self.log.error( + "[MDM] init: can't open audio device. Exit", e=err ) sys.exit(1) try: - structlog.get_logger("structlog").debug( + self.log.debug( "[MDM] init: starting pyaudio callback" ) # self.audio_stream.start_stream() self.stream.start() - except Exception as e: - structlog.get_logger("structlog").error( - "[MDM] init: starting pyaudio callback failed", e=e + except Exception as err: + self.log.error( + "[MDM] init: starting pyaudio callback failed", e=err ) else: @@ -235,9 +236,9 @@ class RF: try: os.mkfifo(RXCHANNEL) os.mkfifo(TXCHANNEL) - except Exception as e: - structlog.get_logger("structlog").error( - f"[MDM] init:mkfifo: Exception: {e}" + except Exception as err: + self.log.error( + f"[MDM] init:mkfifo: Exception: {err}" ) mkfifo_write_callback_thread = threading.Thread( @@ -247,7 +248,7 @@ class RF: ) mkfifo_write_callback_thread.start() - structlog.get_logger("structlog").debug( + self.log.debug( "[MDM] Starting mkfifo_read_callback" ) mkfifo_read_callback_thread = threading.Thread( @@ -320,7 +321,7 @@ class RF: ) hamlib_thread.start() - # structlog.get_logger("structlog").debug("[MDM] Starting worker_receive") + # self.log.debug("[MDM] Starting worker_receive") worker_received = threading.Thread( target=self.worker_received, name="WORKER_THREAD", daemon=True ) @@ -397,7 +398,7 @@ class RF: status: """ - # structlog.get_logger("structlog").debug("[MDM] callback") + # self.log.debug("[MDM] callback") x = np.frombuffer(data_in48k, dtype=np.int16) x = self.resampler.resample48_to_8(x) @@ -429,8 +430,10 @@ class RF: try: outdata[:] = data_out48k[:frames] - except IndexError as e: - structlog.get_logger("structlog").debug(f"[MDM] callback: IndexError: {e}") + except IndexError as err: + self.log.debug( + f"[MDM] callback: IndexError: {err}" + ) # return (data_out48k, audio.pyaudio.paContinue) @@ -447,7 +450,7 @@ class RF: frames: """ - structlog.get_logger("structlog").debug("[MDM] transmit", mode=mode) + self.log.debug("[MDM] transmit", mode=mode) static.TRANSMITTING = True # Toggle ptt early to save some time and send ptt state via socket static.PTT_STATE = self.hamlib.set_ptt(True) @@ -487,7 +490,7 @@ class RF: mod_out_silence = ctypes.create_string_buffer(data_delay * 2) txbuffer = bytes(mod_out_silence) - structlog.get_logger("structlog").debug( + self.log.debug( "[MDM] TRANSMIT", mode=self.MODE, payload=payload_bytes_per_frame ) @@ -625,7 +628,7 @@ class RF: audiobuffer.pop(nin) nin = codec2.api.freedv_nin(freedv) if nbytes == bytes_per_frame: - structlog.get_logger("structlog").debug( + self.log.debug( "[MDM] [demod_audio] Pushing received data to received_queue" ) self.modem_received_queue.put([bytes_out, freedv, bytes_per_frame]) @@ -688,7 +691,7 @@ class RF: while True: data = self.modem_transmit_queue.get() - structlog.get_logger("structlog").debug( + self.log.debug( "[MDM] worker_transmit", mode=data[0] ) self.transmit( @@ -700,7 +703,7 @@ class RF: """Worker for FIFO queue for processing received frames""" while True: data = self.modem_received_queue.get() - structlog.get_logger("structlog").debug( + self.log.debug( "[MDM] worker_received: received data!" ) # data[0] = bytes_out @@ -783,15 +786,15 @@ class RF: modem_stats_sync = modem_stats_sync.value snr = round(modem_stats_snr, 1) - structlog.get_logger("structlog").info("[MDM] calculate_snr: ", snr=snr) + self.log.info("[MDM] calculate_snr: ", snr=snr) # static.SNR = np.clip(snr, 0, 255) # limit to max value of 255 static.SNR = np.clip( snr, -128, 128 ) # limit to max value of -128/128 as a possible fix of #188 return static.SNR - except Exception as e: - structlog.get_logger("structlog").error( - f"[MDM] calculate_snr: Exception: {e}" + except Exception as err: + self.log.error( + f"[MDM] calculate_snr: Exception: {err}" ) static.SNR = 0 return static.SNR @@ -813,7 +816,7 @@ class RF: def calculate_fft(self) -> None: """ Calculate an average signal strength of the channel to assess - whether the channel is 'busy.' + whether the channel is "busy." """ # Initialize channel_busy_delay counter channel_busy_delay = 0 @@ -870,11 +873,11 @@ class RF: dfftlist = dfft.tolist() static.FFT = dfftlist[:320] # 320 --> bandwidth 3000 - except Exception as e: - structlog.get_logger("structlog").error( - f"[MDM] calculate_fft: Exception: {e}" + except Exception as err: + self.log.error( + f"[MDM] calculate_fft: Exception: {err}" ) - structlog.get_logger("structlog").debug("[MDM] Setting fft=0") + self.log.debug("[MDM] Setting fft=0") # else 0 static.FFT = [0] diff --git a/tnc/rig.py b/tnc/rig.py index cdbf879f..6f78b6c9 100644 --- a/tnc/rig.py +++ b/tnc/rig.py @@ -1,11 +1,12 @@ -#!/usr/bin/env python3 - -import sys -import re -import structlog import atexit -import subprocess import os +import re +import subprocess +import sys + +import structlog + +mainlog = structlog.get_logger(__file__) # set global hamlib version hamlib_version = 0 @@ -20,26 +21,26 @@ else: # try importing hamlib try: # get python version - python_version = str(sys.version_info[0]) + "." + str(sys.version_info[1]) + python_version = f"{str(sys.version_info[0])}.{str(sys.version_info[1])}" # installation path for Ubuntu 20.04 LTS python modules - #sys.path.append('/usr/local/lib/python'+ python_version +'/site-packages') + # sys.path.append(f"/usr/local/lib/python{python_version}/site-packages") # installation path for Ubuntu 20.10 + - sys.path.append('/usr/local/lib/') + sys.path.append("/usr/local/lib/") # installation path for Suse - sys.path.append('/usr/local/lib64/python'+ python_version +'/site-packages') + sys.path.append(f"/usr/local/lib64/python{python_version}/site-packages") # everything else... not nice, but an attempt to see how its running within app bundle # this is not needed as python will be shipped with app bundle - sys.path.append('/usr/local/lib/python3.6/site-packages') - sys.path.append('/usr/local/lib/python3.7/site-packages') - sys.path.append('/usr/local/lib/python3.8/site-packages') - sys.path.append('/usr/local/lib/python3.9/site-packages') - sys.path.append('/usr/local/lib/python3.10/site-packages') + sys.path.append("/usr/local/lib/python3.6/site-packages") + sys.path.append("/usr/local/lib/python3.7/site-packages") + sys.path.append("/usr/local/lib/python3.8/site-packages") + sys.path.append("/usr/local/lib/python3.9/site-packages") + sys.path.append("/usr/local/lib/python3.10/site-packages") - sys.path.append('lib/hamlib/linux/python3.8/site-packages') + sys.path.append("lib/hamlib/linux/python3.8/site-packages") import Hamlib # https://stackoverflow.com/a/4703409 @@ -48,43 +49,68 @@ try: min_hamlib_version = 4.1 if hamlib_version > min_hamlib_version: - structlog.get_logger("structlog").info("[RIG] Hamlib found", version=hamlib_version) + mainlog.info("[RIG] Hamlib found", version=hamlib_version) else: - structlog.get_logger("structlog").warning("[RIG] Hamlib outdated", found=hamlib_version, recommend=min_hamlib_version) -except Exception as e: - structlog.get_logger("structlog").warning("[RIG] Python Hamlib binding not found", error=e) + mainlog.warning( + "[RIG] Hamlib outdated", found=hamlib_version, recommend=min_hamlib_version + ) +except Exception as err: + mainlog.warning("[RIG] Python Hamlib binding not found", error=err) try: - structlog.get_logger("structlog").warning("[RIG] Trying to open rigctl") - rigctl = subprocess.Popen("rigctl -V",shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True) + mainlog.warning("[RIG] Trying to open rigctl") + rigctl = subprocess.Popen( + "rigctl -V", + shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + text=True, + ) hamlib_version = rigctl.stdout.readline() - hamlib_version = hamlib_version.split(' ') + hamlib_version = hamlib_version.split(" ") - if hamlib_version[1] == 'Hamlib': - structlog.get_logger("structlog").warning("[RIG] Rigctl found! Please try using this", version=hamlib_version[2]) + if hamlib_version[1] == "Hamlib": + mainlog.warning( + "[RIG] Rigctl found! Please try using this", version=hamlib_version[2] + ) sys.exit() else: raise Exception - except Exception as e: - structlog.get_logger("structlog").critical("[RIG] HAMLIB NOT INSTALLED", error=e) + except Exception as err1: + mainlog.critical("[RIG] HAMLIB NOT INSTALLED", error=err1) hamlib_version = 0 sys.exit() class radio: """ """ - def __init__(self): - self.devicename = '' - self.devicenumber = '' - self.deviceport = '' - self.serialspeed = '' - self.hamlib_ptt_type = '' - self.my_rig = '' - self.pttport = '' - self.data_bits = '' - self.stop_bits = '' - self.handshake = '' - def open_rig(self, devicename, deviceport, hamlib_ptt_type, serialspeed, pttport, data_bits, stop_bits, handshake, rigctld_port, rigctld_ip): + log = structlog.get_logger(__name__) + + def __init__(self): + self.devicename = "" + self.devicenumber = "" + self.deviceport = "" + self.serialspeed = "" + self.hamlib_ptt_type = "" + self.my_rig = "" + self.pttport = "" + self.data_bits = "" + self.stop_bits = "" + self.handshake = "" + + def open_rig( + self, + devicename, + deviceport, + hamlib_ptt_type, + serialspeed, + pttport, + data_bits, + stop_bits, + handshake, + rigctld_port, + rigctld_ip, + ): """ Args: @@ -99,12 +125,11 @@ class radio: rigctld_port: rigctld_ip: - Returns: - """ self.devicename = devicename self.deviceport = str(deviceport) - self.serialspeed = str(serialspeed) # we need to ensure this is a str, otherwise set_conf functions are crashing + # we need to ensure this is a str, otherwise set_conf functions are crashing + self.serialspeed = str(serialspeed) self.hamlib_ptt_type = str(hamlib_ptt_type) self.pttport = str(pttport) self.data_bits = str(data_bits) @@ -118,8 +143,8 @@ class radio: # get devicenumber by looking for deviceobject in Hamlib module try: self.devicenumber = int(getattr(Hamlib, self.devicename)) - except: - structlog.get_logger("structlog").error("[RIG] Hamlib: rig not supported...") + except Exception: + self.log.error("[RIG] Hamlib: rig not supported...") self.devicenumber = 0 self.my_rig = Hamlib.Rig(self.devicenumber) @@ -131,73 +156,84 @@ class radio: self.my_rig.set_conf("data_bits", self.data_bits) self.my_rig.set_conf("ptt_pathname", self.pttport) - if self.hamlib_ptt_type == 'RIG': + if self.hamlib_ptt_type == "RIG": self.hamlib_ptt_type = Hamlib.RIG_PTT_RIG - self.my_rig.set_conf("ptt_type", 'RIG') + self.my_rig.set_conf("ptt_type", "RIG") - elif self.hamlib_ptt_type == 'USB': + elif self.hamlib_ptt_type == "USB": self.hamlib_ptt_type = Hamlib.RIG_PORT_USB - self.my_rig.set_conf("ptt_type", 'USB') + self.my_rig.set_conf("ptt_type", "USB") - elif self.hamlib_ptt_type == 'DTR-H': + elif self.hamlib_ptt_type == "DTR-H": self.hamlib_ptt_type = Hamlib.RIG_PTT_SERIAL_DTR self.my_rig.set_conf("dtr_state", "HIGH") self.my_rig.set_conf("ptt_type", "DTR") - elif self.hamlib_ptt_type == 'DTR-L': + elif self.hamlib_ptt_type == "DTR-L": self.hamlib_ptt_type = Hamlib.RIG_PTT_SERIAL_DTR self.my_rig.set_conf("dtr_state", "LOW") self.my_rig.set_conf("ptt_type", "DTR") - elif self.hamlib_ptt_type == 'RTS': + elif self.hamlib_ptt_type == "RTS": self.hamlib_ptt_type = Hamlib.RIG_PTT_SERIAL_RTS self.my_rig.set_conf("dtr_state", "OFF") self.my_rig.set_conf("ptt_type", "RTS") - elif self.hamlib_ptt_type == 'PARALLEL': + elif self.hamlib_ptt_type == "PARALLEL": self.hamlib_ptt_type = Hamlib.RIG_PTT_PARALLEL - elif self.hamlib_ptt_type == 'MICDATA': + elif self.hamlib_ptt_type == "MICDATA": self.hamlib_ptt_type = Hamlib.RIG_PTT_RIG_MICDATA - elif self.hamlib_ptt_type == 'CM108': + elif self.hamlib_ptt_type == "CM108": self.hamlib_ptt_type = Hamlib.RIG_PTT_CM108 - elif self.hamlib_ptt_type == 'RIG_PTT_NONE': + elif self.hamlib_ptt_type == "RIG_PTT_NONE": self.hamlib_ptt_type = Hamlib.RIG_PTT_NONE - else: #self.hamlib_ptt_type == 'RIG_PTT_NONE': + else: # self.hamlib_ptt_type == "RIG_PTT_NONE": self.hamlib_ptt_type = Hamlib.RIG_PTT_NONE - structlog.get_logger("structlog").info("[RIG] Opening...", device=self.devicenumber, path=self.my_rig.get_conf("rig_pathname"), serial_speed=self.my_rig.get_conf("serial_speed"), serial_handshake=self.my_rig.get_conf("serial_handshake"), stop_bits=self.my_rig.get_conf("stop_bits"), data_bits=self.my_rig.get_conf("data_bits"), ptt_pathname=self.my_rig.get_conf("ptt_pathname")) + self.log.info( + "[RIG] Opening...", + device=self.devicenumber, + path=self.my_rig.get_conf("rig_pathname"), + serial_speed=self.my_rig.get_conf("serial_speed"), + serial_handshake=self.my_rig.get_conf("serial_handshake"), + stop_bits=self.my_rig.get_conf("stop_bits"), + data_bits=self.my_rig.get_conf("data_bits"), + ptt_pathname=self.my_rig.get_conf("ptt_pathname"), + ) self.my_rig.open() atexit.register(self.my_rig.close) try: # lets determine the error message when opening rig - error = str(Hamlib.rigerror(my_rig.error_status)).splitlines() - error = error[1].split('err=') + error = str(Hamlib.rigerror(self.my_rig.error_status)).splitlines() + error = error[1].split("err=") error = error[1] - if error == 'Permission denied': - structlog.get_logger("structlog").error("[RIG] Hamlib has no permissions", e = error) - help_url = 'https://github.com/DJ2LS/FreeDATA/wiki/UBUNTU-Manual-installation#1-permissions' - structlog.get_logger("structlog").error("[RIG] HELP:", check = help_url) - except: - structlog.get_logger("structlog").info("[RIG] Hamlib device opened", status='SUCCESS') + if error == "Permission denied": + self.log.error("[RIG] Hamlib has no permissions", e=error) + help_url = "https://github.com/DJ2LS/FreeDATA/wiki/UBUNTU-Manual-installation#1-permissions" + self.log.error("[RIG] HELP:", check=help_url) + except Exception: + self.log.info("[RIG] Hamlib device opened", status="SUCCESS") # set ptt to false if ptt is stuck for some reason self.set_ptt(False) # set rig mode to USB # temporarly outcommented because of possible problems. - #self.my_rig.set_mode(Hamlib.RIG_MODE_USB) + # self.my_rig.set_mode(Hamlib.RIG_MODE_USB) # self.my_rig.set_mode(Hamlib.RIG_MODE_PKTUSB) return True - except Exception as e: - structlog.get_logger("structlog").error("[RIG] Hamlib - can't open rig", error=e, e=sys.exc_info()[0]) + except Exception as err2: + mainlog.error( + "[RIG] Hamlib - can't open rig", error=err2, e=sys.exc_info()[0] + ) return False def get_frequency(self): @@ -206,16 +242,16 @@ class radio: def get_mode(self): """ """ - (hamlib_mode, bandwith) = self.my_rig.get_mode() + (hamlib_mode, bandwidth) = self.my_rig.get_mode() return Hamlib.rig_strrmode(hamlib_mode) def get_bandwith(self): """ """ - (hamlib_mode, bandwith) = self.my_rig.get_mode() - return bandwith + (hamlib_mode, bandwidth) = self.my_rig.get_mode() + return bandwidth # not needed yet beacuse of some possible problems - #def set_mode(self, mode): + # def set_mode(self, mode): # return 0 def get_ptt(self): diff --git a/tnc/rigctl.py b/tnc/rigctl.py index 319d511a..c8cfa827 100644 --- a/tnc/rigctl.py +++ b/tnc/rigctl.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # Intially created by Franco Spinelli, IW2DHW, 01/2022 # Updated by DJ2LS # @@ -22,19 +21,35 @@ hamlib_version = 0 class radio: """ """ - def __init__(self): - self.devicename = '' - self.devicenumber = '' - self.deviceport = '' - self.serialspeed = '' - self.hamlib_ptt_type = '' - self.my_rig = '' - self.pttport = '' - self.data_bits = '' - self.stop_bits = '' - self.handshake = '' - def open_rig(self, devicename, deviceport, hamlib_ptt_type, serialspeed, pttport, data_bits, stop_bits, handshake, rigctld_ip, rigctld_port): + log = structlog.get_logger(__name__) + + def __init__(self): + self.devicename = "" + self.devicenumber = "" + self.deviceport = "" + self.serialspeed = "" + self.hamlib_ptt_type = "" + self.my_rig = "" + self.pttport = "" + self.data_bits = "" + self.stop_bits = "" + self.handshake = "" + self.cmd = "" + + def open_rig( + self, + devicename, + deviceport, + hamlib_ptt_type, + serialspeed, + pttport, + data_bits, + stop_bits, + handshake, + rigctld_ip, + rigctld_port, + ): """ Args: @@ -54,14 +69,15 @@ class radio: """ self.devicename = devicename self.deviceport = deviceport - self.serialspeed = str(serialspeed) # we need to ensure this is a str, otherwise set_conf functions are crashing + # we need to ensure this is a str, otherwise set_conf functions are crashing + self.serialspeed = str(serialspeed) self.hamlib_ptt_type = hamlib_ptt_type self.pttport = pttport self.data_bits = data_bits self.stop_bits = stop_bits self.handshake = handshake - # check if we are running in a pyinstaller environment + # check if we are running in a pyinstaller environment if hasattr(sys, "_MEIPASS"): sys.path.append(getattr(sys, "_MEIPASS")) else: @@ -70,27 +86,41 @@ class radio: # get devicenumber by looking for deviceobject in Hamlib module try: import Hamlib + self.devicenumber = int(getattr(Hamlib, self.devicename)) - except Exception as e: + except Exception as err: if int(self.devicename): self.devicenumber = int(self.devicename) else: - self.devicenumber = 6 #dummy - structlog.get_logger("structlog").warning("[RIGCTL] Radio not found. Using DUMMY!", error=e) + self.devicenumber = 6 # dummy + self.log.warning("[RIGCTL] Radio not found. Using DUMMY!", error=err) # set deviceport to dummy port, if we selected dummy model - if self.devicenumber == 1 or self.devicenumber == 6: - self.deviceport = '/dev/ttyUSB0' + if self.devicenumber in {1, 6}: + self.deviceport = "/dev/ttyUSB0" print(self.devicenumber, self.deviceport, self.serialspeed) # select precompiled executable for win32/win64 rigctl # this is really a hack...somewhen we need a native hamlib integration for windows - if sys.platform in ['win32', 'win64']: - self.cmd = app_path + 'lib\\hamlib\\'+sys.platform+'\\rigctl -m %d -r %s -s %d ' % (int(self.devicenumber), self.deviceport, int(self.serialspeed)) + if sys.platform in ["win32", "win64"]: + self.cmd = ( + app_path + + "lib\\hamlib\\" + + sys.platform + + ( + f"\\rigctl -m {self.devicenumber} " + f"-r {self.deviceport} " + f"-s {int(self.serialspeed)} " + ) + ) else: - self.cmd = 'rigctl -m %d -r %s -s %d ' % (int(self.devicenumber), self.deviceport, int(self.serialspeed)) + self.cmd = "rigctl -m %d -r %s -s %d " % ( + self.devicenumber, + self.deviceport, + int(self.serialspeed), + ) # eseguo semplicemente rigctl con il solo comando T 1 o T 0 per # il set e t per il get @@ -101,33 +131,35 @@ class radio: def get_frequency(self): """ """ - cmd = self.cmd + ' f' - sw_proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True) + cmd = f"{self.cmd} f" + sw_proc = subprocess.Popen( + cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True + ) time.sleep(0.5) freq = sw_proc.communicate()[0] - #print('get_frequency', freq, sw_proc.communicate()) + # print("get_frequency", freq, sw_proc.communicate()) try: return int(freq) - except: + except Exception: return False def get_mode(self): """ """ - #(hamlib_mode, bandwith) = self.my_rig.get_mode() - #return Hamlib.rig_strrmode(hamlib_mode) + # (hamlib_mode, bandwith) = self.my_rig.get_mode() + # return Hamlib.rig_strrmode(hamlib_mode) try: - return 'PKTUSB' - except: + return "PKTUSB" + except Exception: return False def get_bandwith(self): """ """ - #(hamlib_mode, bandwith) = self.my_rig.get_mode() + # (hamlib_mode, bandwith) = self.my_rig.get_mode() bandwith = 2700 try: return bandwith - except: + except Exception: return False def set_mode(self, mode): @@ -144,14 +176,16 @@ class radio: def get_ptt(self): """ """ - cmd = self.cmd + ' t' - sw_proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True) + cmd = f"{self.cmd} t" + sw_proc = subprocess.Popen( + cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True + ) time.sleep(0.5) status = sw_proc.communicate()[0] try: return status - except: + except Exception: return False def set_ptt(self, state): @@ -163,21 +197,18 @@ class radio: Returns: """ - cmd = self.cmd + ' T ' - print('set_ptt', state) - if state: - cmd = cmd + '1' - else: - cmd = cmd + '0' - print('set_ptt', cmd) + cmd = f"{self.cmd} T " + print("set_ptt", state) + cmd = f"{cmd}1" if state else f"{cmd}0" + print("set_ptt", cmd) sw_proc = subprocess.Popen(cmd, shell=True, text=True) try: return state - except: + except Exception: return False def close_rig(self): """ """ - #self.my_rig.close() + # self.my_rig.close() return diff --git a/tnc/rigctld.py b/tnc/rigctld.py index 9405b019..b5e6f674 100644 --- a/tnc/rigctld.py +++ b/tnc/rigctld.py @@ -4,34 +4,45 @@ # # modified and adjusted to FreeDATA needs by DJ2LS -import logging import socket import time import structlog -import log_handler -import static - # set global hamlib version hamlib_version = 0 -class radio(): +class radio: """rigctld (hamlib) communication class""" + # Note: This is a massive hack. + log = structlog.get_logger(__name__) + def __init__(self, hostname="localhost", port=4532, poll_rate=5, timeout=5): - """ Open a connection to rotctld, and test it for validity """ + """Open a connection to rotctld, and test it for validity""" self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - #self.sock.settimeout(timeout) + # self.sock.settimeout(timeout) self.connected = False self.hostname = hostname self.port = port self.connection_attempts = 5 - def open_rig(self, devicename, deviceport, hamlib_ptt_type, serialspeed, pttport, data_bits, stop_bits, handshake, rigctld_ip, rigctld_port): + def open_rig( + self, + devicename, + deviceport, + hamlib_ptt_type, + serialspeed, + pttport, + data_bits, + stop_bits, + handshake, + rigctld_ip, + rigctld_port, + ): """ Args: @@ -51,26 +62,35 @@ class radio(): """ self.hostname = rigctld_ip self.port = int(rigctld_port) - + if self.connect(): - logging.debug("Rigctl intialized") + self.log.debug("Rigctl initialized") return True - structlog.get_logger("structlog").error("[RIGCTLD] Can't connect to rigctld!", ip=self.hostname, port=self.port) + self.log.error( + "[RIGCTLD] Can't connect to rigctld!", ip=self.hostname, port=self.port + ) return False def connect(self): """Connect to rigctld instance""" if not self.connected: try: - self.connection = socket.create_connection((self.hostname,self.port)) + self.connection = socket.create_connection((self.hostname, self.port)) self.connected = True - structlog.get_logger("structlog").info("[RIGCTLD] Connected to rigctld!", ip=self.hostname, port=self.port) + self.log.info( + "[RIGCTLD] Connected to rigctld!", ip=self.hostname, port=self.port + ) return True - except Exception as e: + except Exception as err: # ConnectionRefusedError: [Errno 111] Connection refused self.close_rig() - structlog.get_logger("structlog").warning("[RIGCTLD] Connection to rigctld refused! Reconnect...", ip=self.hostname, port=self.port, e=e) + self.log.warning( + "[RIGCTLD] Connection to rigctld refused! Reconnect...", + ip=self.hostname, + port=self.port, + e=err, + ) return False def close_rig(self): @@ -85,20 +105,28 @@ class radio(): Args: command: - Returns: - """ if self.connected: try: - self.connection.sendall(command+b'\n') - except: - structlog.get_logger("structlog").warning("[RIGCTLD] Command not executed!", command=command, ip=self.hostname, port=self.port) + self.connection.sendall(command + b"\n") + except Exception: + self.log.warning( + "[RIGCTLD] Command not executed!", + command=command, + ip=self.hostname, + port=self.port, + ) self.connected = False try: return self.connection.recv(1024) - except: - structlog.get_logger("structlog").warning("[RIGCTLD] No command response!", command=command, ip=self.hostname, port=self.port) + except Exception: + self.log.warning( + "[RIGCTLD] No command response!", + command=command, + ip=self.hostname, + port=self.port, + ) self.connected = False else: @@ -110,20 +138,20 @@ class radio(): """ """ try: data = self.send_command(b"m") - data = data.split(b'\n') + data = data.split(b"\n") mode = data[0] return mode.decode("utf-8") - except: + except Exception: return 0 def get_bandwith(self): """ """ try: data = self.send_command(b"m") - data = data.split(b'\n') + data = data.split(b"\n") bandwith = data[1] return bandwith.decode("utf-8") - except: + except Exception: return 0 def get_frequency(self): @@ -131,14 +159,14 @@ class radio(): try: frequency = self.send_command(b"f") return frequency.decode("utf-8") - except: + except Exception: return 0 def get_ptt(self): """ """ try: return self.send_command(b"t") - except: + except Exception: return False def set_ptt(self, state): @@ -152,9 +180,9 @@ class radio(): """ try: if state: - self.send_command(b"T 1") + self.send_command(b"T 1") else: - self.send_command(b"T 0") + self.send_command(b"T 0") return state - except: + except Exception: return False diff --git a/tnc/rigdummy.py b/tnc/rigdummy.py index 445cfb58..22572356 100644 --- a/tnc/rigdummy.py +++ b/tnc/rigdummy.py @@ -1,12 +1,9 @@ -#!/usr/bin/env python3 - -import structlog - hamlib_version = 0 class radio: """ """ + def __init__(self): pass diff --git a/tnc/sock.py b/tnc/sock.py index 604e41de..fcaa86f6 100644 --- a/tnc/sock.py +++ b/tnc/sock.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Created on Fri Dec 25 21:25:14 2020 @@ -21,23 +20,17 @@ Created on Fri Dec 25 21:25:14 2020 """ import atexit import base64 -import logging -import os import queue import socketserver import sys import threading import time -import psutil -import structlog -import ujson as json - -import audio import data_handler import helpers -import log_handler import static +import structlog +import ujson as json SOCKET_QUEUE = queue.Queue() DAEMON_QUEUE = queue.Queue() @@ -50,19 +43,22 @@ class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): """ the socket handler base class """ + pass class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): """ """ + connection_alive = False + log = structlog.get_logger(__name__) def send_to_client(self): """ function called by socket handler send data to a network client if available """ - tempdata = b'' + tempdata = b"" while self.connection_alive and not CLOSE_SIGNAL: # send tnc state as network stream # check server port against daemon port and send corresponding data @@ -80,24 +76,24 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): while not SOCKET_QUEUE.empty(): data = SOCKET_QUEUE.get() - sock_data = bytes(data, 'utf-8') - sock_data += b'\n' # append line limiter + sock_data = bytes(data, "utf-8") + sock_data += b"\n" # append line limiter # send data to all clients - #try: + # try: for client in CONNECTED_CLIENTS: try: client.send(sock_data) - except Exception as e: + except Exception as err: # print("connection lost...") - structlog.get_logger("structlog").info("[SCK] Connection lost", e=e) + self.log.info("[SCK] Connection lost", e=err) self.connection_alive = False # we want to transmit scatter data only once to reduce network traffic static.SCATTER = [] # we want to display INFO messages only once static.INFO = [] - #self.request.sendall(sock_data) + # self.request.sendall(sock_data) time.sleep(0.15) def receive_from_client(self): @@ -111,15 +107,15 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): chunk = self.request.recv(1024) data += chunk - if chunk == b'': - #print("connection broken. Closing...") + if chunk == b"": + # print("connection broken. Closing...") self.connection_alive = False - if data.startswith(b'{') and data.endswith(b'}\n'): + if data.startswith(b"{") and data.endswith(b"}\n"): # split data by \n if we have multiple commands in socket buffer - data = data.split(b'\n') + data = data.split(b"\n") # remove empty data - data.remove(b'') + data.remove(b"") # iterate thorugh data list for commands in data: @@ -137,8 +133,13 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): # finally delete our rx buffer to be ready for new commands data = bytes() - except Exception as e: - structlog.get_logger("structlog").info("[SCK] Connection closed", ip=self.client_address[0], port=self.client_address[1], e=e) + except Exception as err: + self.log.info( + "[SCK] Connection closed", + ip=self.client_address[0], + port=self.client_address[1], + e=err, + ) self.connection_alive = False def handle(self): @@ -147,11 +148,19 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): """ CONNECTED_CLIENTS.add(self.request) - structlog.get_logger("structlog").debug("[SCK] Client connected", ip=self.client_address[0], port=self.client_address[1]) + self.log.debug( + "[SCK] Client connected", + ip=self.client_address[0], + port=self.client_address[1], + ) self.connection_alive = True - self.sendThread = threading.Thread(target=self.send_to_client, args=[],daemon=True).start() - self.receiveThread = threading.Thread(target=self.receive_from_client, args=[],daemon=True).start() + self.sendThread = threading.Thread( + target=self.send_to_client, args=[], daemon=True + ).start() + self.receiveThread = threading.Thread( + target=self.receive_from_client, args=[], daemon=True + ).start() # keep connection alive until we close it while self.connection_alive and not CLOSE_SIGNAL: @@ -159,11 +168,19 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): def finish(self): """ """ - structlog.get_logger("structlog").warning("[SCK] Closing client socket", ip=self.client_address[0], port=self.client_address[1]) + self.log.warning( + "[SCK] Closing client socket", + ip=self.client_address[0], + port=self.client_address[1], + ) try: CONNECTED_CLIENTS.remove(self.request) - except: - structlog.get_logger("structlog").warning("[SCK] client connection already removed from client list", client=self.request) + except Exception: + self.log.warning( + "[SCK] client connection already removed from client list", + client=self.request, + ) + def process_tnc_commands(data): """ @@ -175,64 +192,82 @@ def process_tnc_commands(data): Returns: """ + log = structlog.get_logger(__name__) + # we need to do some error handling in case of socket timeout or decoding issue try: # convert data to json object received_json = json.loads(data) - structlog.get_logger("structlog").debug("[SCK] CMD", command=received_json) + log.debug("[SCK] CMD", command=received_json) # SET TX AUDIO LEVEL ----------------------------------------------------- - if received_json["type"] == "set" and received_json["command"] == "tx_audio_level": + if ( + received_json["type"] == "set" + and received_json["command"] == "tx_audio_level" + ): try: static.TX_AUDIO_LEVEL = int(received_json["value"]) command_response("tx_audio_level", True) - except Exception as e: + except Exception as err: command_response("tx_audio_level", False) - structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json) + log.warning( + "[SCK] command execution error", e=err, command=received_json + ) # TRANSMIT SINE WAVE ----------------------------------------------------- - if received_json["type"] == "set" and received_json["command"] == "send_test_frame": + if ( + received_json["type"] == "set" + and received_json["command"] == "send_test_frame" + ): try: - data_handler.DATA_QUEUE_TRANSMIT.put(['SEND_TEST_FRAME']) + data_handler.DATA_QUEUE_TRANSMIT.put(["SEND_TEST_FRAME"]) command_response("send_test_frame", True) - except Exception as e: + except Exception as err: command_response("send_test_frame", False) - structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json) + log.warning( + "[SCK] command execution error", e=err, command=received_json + ) # CQ CQ CQ ----------------------------------------------------- if received_json["command"] == "cqcqcq": try: - data_handler.DATA_QUEUE_TRANSMIT.put(['CQ']) + data_handler.DATA_QUEUE_TRANSMIT.put(["CQ"]) command_response("cqcqcq", True) - except Exception as e: + except Exception as err: command_response("cqcqcq", False) - structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json) + log.warning( + "[SCK] command execution error", e=err, command=received_json + ) # START_BEACON ----------------------------------------------------- if received_json["command"] == "start_beacon": try: static.BEACON_STATE = True interval = int(received_json["parameter"]) - data_handler.DATA_QUEUE_TRANSMIT.put(['BEACON', interval, True]) + data_handler.DATA_QUEUE_TRANSMIT.put(["BEACON", interval, True]) command_response("start_beacon", True) - except Exception as e: + except Exception as err: command_response("start_beacon", False) - structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json) + log.warning( + "[SCK] command execution error", e=err, command=received_json + ) # STOP_BEACON ----------------------------------------------------- if received_json["command"] == "stop_beacon": try: - structlog.get_logger("structlog").warning("[TNC] Stopping beacon!") + log.warning("[TNC] Stopping beacon!") static.BEACON_STATE = False - data_handler.DATA_QUEUE_TRANSMIT.put(['BEACON', None, False]) + data_handler.DATA_QUEUE_TRANSMIT.put(["BEACON", None, False]) command_response("stop_beacon", True) - except Exception as e: + except Exception as err: command_response("stop_beacon", False) - structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json) + log.warning( + "[SCK] command execution error", e=err, command=received_json + ) # PING ---------------------------------------------------------- - if received_json["type"] == 'ping' and received_json["command"] == "ping": + if received_json["type"] == "ping" and received_json["command"] == "ping": # send ping frame and wait for ACK try: dxcallsign = received_json["dxcallsign"] @@ -243,14 +278,16 @@ def process_tnc_commands(data): dxcallsign = helpers.callsign_to_bytes(dxcallsign) dxcallsign = helpers.bytes_to_callsign(dxcallsign) - data_handler.DATA_QUEUE_TRANSMIT.put(['PING', dxcallsign]) + data_handler.DATA_QUEUE_TRANSMIT.put(["PING", dxcallsign]) command_response("ping", True) - except Exception as e: + except Exception as err: command_response("ping", False) - structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json) + log.warning( + "[SCK] command execution error", e=err, command=received_json + ) # CONNECT ---------------------------------------------------------- - if received_json["type"] == 'arq' and received_json["command"] == "connect": + if received_json["type"] == "arq" and received_json["command"] == "connect": static.BEACON_PAUSE = True # send ping frame and wait for ACK try: @@ -265,24 +302,28 @@ def process_tnc_commands(data): static.DXCALLSIGN = dxcallsign static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN) - data_handler.DATA_QUEUE_TRANSMIT.put(['CONNECT', dxcallsign]) + data_handler.DATA_QUEUE_TRANSMIT.put(["CONNECT", dxcallsign]) command_response("connect", True) - except Exception as e: + except Exception as err: command_response("connect", False) - structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json) + log.warning( + "[SCK] command execution error", e=err, command=received_json + ) # DISCONNECT ---------------------------------------------------------- - if received_json["type"] == 'arq' and received_json["command"] == "disconnect": + if received_json["type"] == "arq" and received_json["command"] == "disconnect": # send ping frame and wait for ACK try: - data_handler.DATA_QUEUE_TRANSMIT.put(['DISCONNECT']) + data_handler.DATA_QUEUE_TRANSMIT.put(["DISCONNECT"]) command_response("disconnect", True) - except Exception as e: + except Exception as err: command_response("disconnect", False) - structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json) + log.warning( + "[SCK] command execution error", e=err, command=received_json + ) # TRANSMIT RAW DATA ------------------------------------------- - if received_json["type"] == 'arq' and received_json["command"] == "send_raw": + if received_json["type"] == "arq" and received_json["command"] == "send_raw": static.BEACON_PAUSE = True try: if not static.ARQ_SESSION: @@ -306,39 +347,48 @@ def process_tnc_commands(data): # check if specific callsign is set with different SSID than the TNC is initialized try: mycallsign = received_json["parameter"][0]["mycallsign"] - except: + except Exception: mycallsign = static.MYCALLSIGN # check if transmission uuid provided else set no-uuid try: arq_uuid = received_json["uuid"] - except: - arq_uuid = 'no-uuid' + except Exception: + arq_uuid = "no-uuid" if not len(base64data) % 4: binarydata = base64.b64decode(base64data) - data_handler.DATA_QUEUE_TRANSMIT.put(['ARQ_RAW', binarydata, mode, n_frames, arq_uuid, mycallsign]) + data_handler.DATA_QUEUE_TRANSMIT.put( + ["ARQ_RAW", binarydata, mode, n_frames, arq_uuid, mycallsign] + ) else: raise TypeError - except Exception as e: + except Exception as err: command_response("send_raw", False) - structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json) + log.warning( + "[SCK] command execution error", e=err, command=received_json + ) # STOP TRANSMISSION ---------------------------------------------------------- - if received_json["type"] == 'arq' and received_json["command"] == "stop_transmission": + if ( + received_json["type"] == "arq" + and received_json["command"] == "stop_transmission" + ): try: - if static.TNC_STATE == 'BUSY' or static.ARQ_STATE: - data_handler.DATA_QUEUE_TRANSMIT.put(['STOP']) - structlog.get_logger("structlog").warning("[TNC] Stopping transmission!") - static.TNC_STATE = 'IDLE' + if static.TNC_STATE == "BUSY" or static.ARQ_STATE: + data_handler.DATA_QUEUE_TRANSMIT.put(["STOP"]) + log.warning("[TNC] Stopping transmission!") + static.TNC_STATE = "IDLE" static.ARQ_STATE = False command_response("stop_transmission", True) - except Exception as e: + except Exception as err: command_response("stop_transmission", False) - structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json) + log.warning( + "[SCK] command execution error", e=err, command=received_json + ) - if received_json["type"] == 'get' and received_json["command"] == 'rx_buffer': + if received_json["type"] == "get" and received_json["command"] == "rx_buffer": try: output = { "command": "rx_buffer", @@ -346,37 +396,53 @@ def process_tnc_commands(data): } for i in range(len(static.RX_BUFFER)): - #print(static.RX_BUFFER[i][4]) - #rawdata = json.loads(static.RX_BUFFER[i][4]) + # print(static.RX_BUFFER[i][4]) + # rawdata = json.loads(static.RX_BUFFER[i][4]) base64_data = static.RX_BUFFER[i][4] - output["data-array"].append({"uuid": static.RX_BUFFER[i][0],"timestamp": static.RX_BUFFER[i][1], "dxcallsign": str(static.RX_BUFFER[i][2], 'utf-8'), "dxgrid": str(static.RX_BUFFER[i][3], 'utf-8'), "data": base64_data}) + output["data-array"].append( + { + "uuid": static.RX_BUFFER[i][0], + "timestamp": static.RX_BUFFER[i][1], + "dxcallsign": str(static.RX_BUFFER[i][2], "utf-8"), + "dxgrid": str(static.RX_BUFFER[i][3], "utf-8"), + "data": base64_data, + } + ) jsondata = json.dumps(output) - #self.request.sendall(bytes(jsondata, encoding)) + # self.request.sendall(bytes(jsondata, encoding)) SOCKET_QUEUE.put(jsondata) command_response("rx_buffer", True) - except Exception as e: + except Exception as err: command_response("rx_buffer", False) - structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json) + log.warning( + "[SCK] command execution error", e=err, command=received_json + ) - if received_json["type"] == 'set' and received_json["command"] == 'del_rx_buffer': + if ( + received_json["type"] == "set" + and received_json["command"] == "del_rx_buffer" + ): try: static.RX_BUFFER = [] command_response("del_rx_buffer", True) - except Exception as e: + except Exception as err: command_response("del_rx_buffer", False) - structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json) + log.warning( + "[SCK] command execution error", e=err, command=received_json + ) # exception, if JSON cant be decoded - except Exception as e: - structlog.get_logger("structlog").error("[TNC] JSON decoding error", e=e) + except Exception as err: + log.error("[TNC] JSON decoding error", e=err) + def send_tnc_state(): """ send the tnc state to network """ - encoding = 'utf-8' + encoding = "utf-8" output = { "command": "tnc_state", @@ -401,8 +467,8 @@ def send_tnc_state(): "arq_compression_factor": str(static.ARQ_COMPRESSION_FACTOR), "arq_transmission_percent": str(static.ARQ_TRANSMISSION_PERCENT), "total_bytes": str(static.TOTAL_BYTES), - "info" : static.INFO, - "beacon_state" : str(static.BEACON_STATE), + "info": static.INFO, + "beacon_state": str(static.BEACON_STATE), "stations": [], "mycallsign": str(static.MYCALLSIGN, encoding), "dxcallsign": str(static.DXCALLSIGN, encoding), @@ -411,17 +477,21 @@ def send_tnc_state(): # add heard stations to heard stations object for heard in static.HEARD_STATIONS: - output["stations"].append({ - "dxcallsign": str(heard[0], 'utf-8'), - "dxgrid": str(heard[1], 'utf-8'), - "timestamp": heard[2], - "datatype": heard[3], - "snr": heard[4], - "offset": heard[5], - "frequency": heard[6]}) + output["stations"].append( + { + "dxcallsign": str(heard[0], "utf-8"), + "dxgrid": str(heard[1], "utf-8"), + "timestamp": heard[2], + "datatype": heard[3], + "snr": heard[4], + "offset": heard[5], + "frequency": heard[6], + } + ) return json.dumps(output) + def process_daemon_commands(data): """ process daemon commands @@ -432,41 +502,55 @@ def process_daemon_commands(data): Returns: """ + log = structlog.get_logger(__name__) + # convert data to json object received_json = json.loads(data) - structlog.get_logger("structlog").debug("[SCK] CMD", command=received_json) - if received_json["type"] == 'set' and received_json["command"] == 'mycallsign': + log.debug("[SCK] CMD", command=received_json) + if received_json["type"] == "set" and received_json["command"] == "mycallsign": try: callsign = received_json["parameter"] - if bytes(callsign, 'utf-8') == b'': - self.request.sendall(b'INVALID CALLSIGN') - structlog.get_logger("structlog").warning("[DMN] SET MYCALL FAILED", call=static.MYCALLSIGN, crc=static.MYCALLSIGN_CRC) + if bytes(callsign, "utf-8") == b"": + self.request.sendall(b"INVALID CALLSIGN") + log.warning( + "[DMN] SET MYCALL FAILED", + call=static.MYCALLSIGN, + crc=static.MYCALLSIGN_CRC, + ) else: - static.MYCALLSIGN = bytes(callsign, 'utf-8') + static.MYCALLSIGN = bytes(callsign, "utf-8") static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN) command_response("mycallsign", True) - structlog.get_logger("structlog").info("[DMN] SET MYCALL", call=static.MYCALLSIGN, crc=static.MYCALLSIGN_CRC) - except Exception as e: + log.info( + "[DMN] SET MYCALL", + call=static.MYCALLSIGN, + crc=static.MYCALLSIGN_CRC, + ) + except Exception as err: command_response("mycallsign", False) - structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json) + log.warning("[SCK] command execution error", e=err, command=received_json) - if received_json["type"] == 'set' and received_json["command"] == 'mygrid': + if received_json["type"] == "set" and received_json["command"] == "mygrid": try: mygrid = received_json["parameter"] - if bytes(mygrid, 'utf-8') == b'': - self.request.sendall(b'INVALID GRID') + if bytes(mygrid, "utf-8") == b"": + self.request.sendall(b"INVALID GRID") else: - static.MYGRID = bytes(mygrid, 'utf-8') - structlog.get_logger("structlog").info("[SCK] SET MYGRID", grid=static.MYGRID) + static.MYGRID = bytes(mygrid, "utf-8") + log.info("[SCK] SET MYGRID", grid=static.MYGRID) command_response("mygrid", True) - except Exception as e: + except Exception as err: command_response("mygrid", False) - structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json) + log.warning("[SCK] command execution error", e=err, command=received_json) - if received_json["type"] == 'set' and received_json["command"] == 'start_tnc' and not static.TNCSTARTED: + if ( + received_json["type"] == "set" + and received_json["command"] == "start_tnc" + and not static.TNCSTARTED + ): try: mycall = str(received_json["parameter"][0]["mycall"]) mygrid = str(received_json["parameter"][0]["mygrid"]) @@ -494,40 +578,45 @@ def process_daemon_commands(data): # print some debugging parameters for item in received_json["parameter"][0]: - structlog.get_logger("structlog").debug("[DMN] TNC Startup Config : " + item, value=received_json["parameter"][0][item]) + log.debug( + f"[DMN] TNC Startup Config : {item}", + value=received_json["parameter"][0][item], + ) - DAEMON_QUEUE.put(['STARTTNC', - mycall, - mygrid, - rx_audio, - tx_audio, - devicename, - deviceport, - serialspeed, - pttprotocol, - pttport, - data_bits, - stop_bits, - handshake, - radiocontrol, - rigctld_ip, - rigctld_port, - enable_scatter, - enable_fft, - low_bandwith_mode, - tuning_range_fmin, - tuning_range_fmax, - enable_fsk, - tx_audio_level, - respond_to_cq, - ]) + DAEMON_QUEUE.put( + [ + "STARTTNC", + mycall, + mygrid, + rx_audio, + tx_audio, + devicename, + deviceport, + serialspeed, + pttprotocol, + pttport, + data_bits, + stop_bits, + handshake, + radiocontrol, + rigctld_ip, + rigctld_port, + enable_scatter, + enable_fft, + low_bandwith_mode, + tuning_range_fmin, + tuning_range_fmax, + enable_fsk, + tx_audio_level, + respond_to_cq, + ] + ) command_response("start_tnc", True) - except Exception as e: + except Exception as err: command_response("start_tnc", False) - structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json) - - if received_json["type"] == 'get' and received_json["command"] == 'test_hamlib': + log.warning("[SCK] command execution error", e=err, command=received_json) + if received_json["type"] == "get" and received_json["command"] == "test_hamlib": try: devicename = str(received_json["parameter"][0]["devicename"]) deviceport = str(received_json["parameter"][0]["deviceport"]) @@ -541,56 +630,62 @@ def process_daemon_commands(data): rigctld_ip = str(received_json["parameter"][0]["rigctld_ip"]) rigctld_port = str(received_json["parameter"][0]["rigctld_port"]) - DAEMON_QUEUE.put(['TEST_HAMLIB', - devicename, - deviceport, - serialspeed, - pttprotocol, - pttport, - data_bits, - stop_bits, - handshake, - radiocontrol, - rigctld_ip, - rigctld_port, - ]) + DAEMON_QUEUE.put( + [ + "TEST_HAMLIB", + devicename, + deviceport, + serialspeed, + pttprotocol, + pttport, + data_bits, + stop_bits, + handshake, + radiocontrol, + rigctld_ip, + rigctld_port, + ] + ) command_response("test_hamlib", True) - except Exception as e: + except Exception as err: command_response("test_hamlib", False) - structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json) + log.warning("[SCK] command execution error", e=err, command=received_json) - if received_json["type"] == 'set' and received_json["command"] == 'stop_tnc': + if received_json["type"] == "set" and received_json["command"] == "stop_tnc": try: static.TNCPROCESS.kill() # unregister process from atexit to avoid process zombies atexit.unregister(static.TNCPROCESS.kill) - structlog.get_logger("structlog").warning("[DMN] Stopping TNC") + log.warning("[DMN] Stopping TNC") static.TNCSTARTED = False command_response("stop_tnc", True) - except Exception as e: + except Exception as err: command_response("stop_tnc", False) - structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json) + log.warning("[SCK] command execution error", e=err, command=received_json) + def send_daemon_state(): """ send the daemon state to network """ + log = structlog.get_logger(__name__) + try: python_version = f"{str(sys.version_info[0])}.{str(sys.version_info[1])}" output = { - 'command': 'daemon_state', - 'daemon_state': [], - 'python_version': str(python_version), - 'hamlib_version': static.HAMLIB_VERSION, - 'input_devices': static.AUDIO_INPUT_DEVICES, - 'output_devices': static.AUDIO_OUTPUT_DEVICES, - 'serial_devices': static.SERIAL_DEVICES, + "command": "daemon_state", + "daemon_state": [], + "python_version": str(python_version), + "hamlib_version": static.HAMLIB_VERSION, + "input_devices": static.AUDIO_INPUT_DEVICES, + "output_devices": static.AUDIO_OUTPUT_DEVICES, + "serial_devices": static.SERIAL_DEVICES, #'cpu': str(psutil.cpu_percent()), #'ram': str(psutil.virtual_memory().percent), - 'version': '0.1' - } + "version": "0.1", + } if static.TNCSTARTED: output["daemon_state"].append({"status": "running"}) @@ -600,12 +695,13 @@ def send_daemon_state(): jsondata = json.dumps(output) return jsondata - except Exception as e: - structlog.get_logger("structlog").warning("[SCK] error", e=e) + except Exception as err: + log.warning("[SCK] error", e=err) return None + def command_response(command, status): s_status = "OK" if status else "Failed" - jsondata = {"command_response": command, "status" : s_status} + jsondata = {"command_response": command, "status": s_status} data_out = json.dumps(jsondata) SOCKET_QUEUE.put(data_out) diff --git a/tnc/static.py b/tnc/static.py index 07586fb9..26348fe8 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -8,7 +8,7 @@ Here we are saving application wide variables and stats, which have to be access Not nice, suggestions are appreciated :-) """ -VERSION = '0.4.0-alpha' +VERSION = "0.4.0-alpha" # DAEMON DAEMONPORT = 3001 @@ -16,16 +16,16 @@ TNCSTARTED = False TNCPROCESS = 0 # Operator Defaults -MYCALLSIGN = b'AA0AA' -MYCALLSIGN_CRC = b'A' +MYCALLSIGN = b"AA0AA" +MYCALLSIGN_CRC = b"A" -DXCALLSIGN = b'AA0AA' -DXCALLSIGN_CRC = b'A' +DXCALLSIGN = b"AA0AA" +DXCALLSIGN_CRC = b"A" -MYGRID = b'' -DXGRID = b'' +MYGRID = b"" +DXGRID = b"" -SSID_LIST = [] # ssid list we are responding to +SSID_LIST = [] # ssid list we are responding to LOW_BANDWITH_MODE = False # --------------------------------- @@ -33,7 +33,7 @@ LOW_BANDWITH_MODE = False # Server Defaults HOST = "0.0.0.0" PORT = 3000 -SOCKET_TIMEOUT = 1 # seconds +SOCKET_TIMEOUT = 1 # seconds # --------------------------------- SERIAL_DEVICES = [] # --------------------------------- @@ -41,21 +41,21 @@ SERIAL_DEVICES = [] PTT_STATE = False TRANSMITTING = False -HAMLIB_VERSION = '0' -HAMLIB_PTT_TYPE = 'RTS' -HAMLIB_DEVICE_NAME = 'RIG_MODEL_DUMMY_NOVFO' -HAMLIB_DEVICE_PORT = '/dev/ttyUSB0' -HAMLIB_SERIAL_SPEED = '9600' -HAMLIB_PTT_PORT = '/dev/ttyUSB0' -HAMLIB_STOP_BITS = '1' -HAMLIB_DATA_BITS = '8' -HAMLIB_HANDSHAKE = 'None' -HAMLIB_RADIOCONTROL = 'direct' -HAMLIB_RIGCTLD_IP = '127.0.0.1' -HAMLIB_RIGCTLD_PORT = '4532' +HAMLIB_VERSION = "0" +HAMLIB_PTT_TYPE = "RTS" +HAMLIB_DEVICE_NAME = "RIG_MODEL_DUMMY_NOVFO" +HAMLIB_DEVICE_PORT = "/dev/ttyUSB0" +HAMLIB_SERIAL_SPEED = "9600" +HAMLIB_PTT_PORT = "/dev/ttyUSB0" +HAMLIB_STOP_BITS = "1" +HAMLIB_DATA_BITS = "8" +HAMLIB_HANDSHAKE = "None" +HAMLIB_RADIOCONTROL = "direct" +HAMLIB_RIGCTLD_IP = "127.0.0.1" +HAMLIB_RIGCTLD_PORT = "4532" HAMLIB_FREQUENCY = 0 -HAMLIB_MODE = '' +HAMLIB_MODE = "" HAMLIB_BANDWIDTH = 0 # ------------------------- # FreeDV Defaults @@ -74,7 +74,7 @@ AUDIO_INPUT_DEVICES = [] AUDIO_OUTPUT_DEVICES = [] AUDIO_INPUT_DEVICE = -2 AUDIO_OUTPUT_DEVICE = -2 -BUFFER_OVERFLOW_COUNTER = [0,0,0,0,0] +BUFFER_OVERFLOW_COUNTER = [0, 0, 0, 0, 0] AUDIO_RMS = 0 FFT = [0] @@ -94,11 +94,12 @@ ARQ_TRANSMISSION_PERCENT = 0 ARQ_SPEED_LEVEL = 0 TOTAL_BYTES = 0 -#CHANNEL_STATE = 'RECEIVING_SIGNALLING' -TNC_STATE = 'IDLE' +# CHANNEL_STATE = 'RECEIVING_SIGNALLING' +TNC_STATE = "IDLE" ARQ_STATE = False ARQ_SESSION = False -ARQ_SESSION_STATE = 'disconnected' # disconnected, connecting, connected, disconnecting, failed +# disconnected, connecting, connected, disconnecting, failed +ARQ_SESSION_STATE = "disconnected" # BEACON STATE BEACON_STATE = False @@ -108,8 +109,8 @@ BEACON_PAUSE = False RX_BUFFER = [] RX_MSG_BUFFER = [] RX_BURST_BUFFER = [] -RX_FRAME_BUFFER = b'' -#RX_BUFFER_SIZE = 0 +RX_FRAME_BUFFER = b"" +# RX_BUFFER_SIZE = 0 # ------- HEARD STATIOS BUFFER HEARD_STATIONS = [] From e4ad7bbb1b8edd4fc9f170832ecb3e246a2c87ff Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Sat, 28 May 2022 15:43:25 -0400 Subject: [PATCH 13/26] Remove extra assignment. --- tnc/modem.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tnc/modem.py b/tnc/modem.py index d507119d..e091eb4d 100644 --- a/tnc/modem.py +++ b/tnc/modem.py @@ -222,7 +222,6 @@ class RF: active = True self.stream = Object() - self.stream.active = True # Create mkfifo buffers try: From b0742e114a077faaff9b309f614f117a0c1614a9 Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Sun, 29 May 2022 08:42:38 -0400 Subject: [PATCH 14/26] Correct usage of tempbuffer list. --- tnc/data_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tnc/data_handler.py b/tnc/data_handler.py index 12f53aea..5c84f07b 100644 --- a/tnc/data_handler.py +++ b/tnc/data_handler.py @@ -936,7 +936,8 @@ class DATA: retry=self.tx_n_retry_of_burst, ) - self.enqueue_frame_for_tx(bytearray(tempbuffer), c2_mode=data_mode) + for t_buf_item in tempbuffer: + self.enqueue_frame_for_tx(t_buf_item, c2_mode=data_mode) # After transmission finished, wait for an ACK or RPT frame # burstacktimeout = time.time() + BURST_ACK_TIMEOUT_SECONDS + 100 From 99586c6d196f312a5aa1aac9d5f20db79b594f49 Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Sun, 29 May 2022 09:43:17 -0400 Subject: [PATCH 15/26] Fix for IndexError related to SSID extraction. --- tnc/helpers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tnc/helpers.py b/tnc/helpers.py index 01cb8b26..b8d1b558 100644 --- a/tnc/helpers.py +++ b/tnc/helpers.py @@ -290,6 +290,9 @@ def check_callsign(callsign: bytes, crc_to_check: bytes): # We want the callsign without SSID callsign = callsign.split(b"-")[0] + except IndexError: + # This is expected when `callsign` doesn't have a dash. + pass except Exception as err: log.debug("[HLP] check_callsign: Error callsign SSID to integer:", e=err) From bc6db3950f9c2aee19a4d7a0d4711aeebd62d306 Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Sun, 29 May 2022 09:47:11 -0400 Subject: [PATCH 16/26] Suppress expected exceptions. Fixes issue #196. --- tnc/helpers.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tnc/helpers.py b/tnc/helpers.py index b8d1b558..c5d8ace2 100644 --- a/tnc/helpers.py +++ b/tnc/helpers.py @@ -199,8 +199,12 @@ def callsign_to_bytes(callsign) -> bytes: # Try converting to bytestring if possible type string try: callsign = bytes(callsign, "utf-8") - except TypeError as err: - log.debug("[HLP] callsign_to_bytes: Error converting callsign to bytes:", e=err) + except TypeError: + # This is expected depending on the type of the `callsign` argument. + # log.debug("[HLP] callsign_to_bytes: Error converting callsign to bytes:", e=err) + pass + except Exception as err: + log.debug("[HLP] callsign_to_bytes: Error callsign SSID to integer:", e=err) # Need this step to reduce the needed payload by the callsign # (stripping "-" out of the callsign) @@ -208,7 +212,11 @@ def callsign_to_bytes(callsign) -> bytes: ssid = 0 try: ssid = int(callsign[1]) - except IndexError as err: + except IndexError: + # This is expected when callsign doesn't have a dash. + # log.debug("[HLP] callsign_to_bytes: Error callsign SSID to integer:", e=err) + pass + except Exception as err: log.debug("[HLP] callsign_to_bytes: Error callsign SSID to integer:", e=err) # callsign = callsign[0] From ae3fafde8da1faff5711313c8859375f5a4d1677 Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Mon, 30 May 2022 11:41:24 -0400 Subject: [PATCH 17/26] Implement `send_data_to_socket_queue`. Implements capability requested in issue 200. Remove similar duplicated code in `arq_data_received`, `arq_transmit`, `frame_nack_received`, `arq_open_data_channel`, `received_ping_ack`, `received_beacon` and `received_qrv`. --- tnc/data_handler.py | 209 ++++++++++++++++++++++---------------------- 1 file changed, 105 insertions(+), 104 deletions(-) diff --git a/tnc/data_handler.py b/tnc/data_handler.py index 5c84f07b..c076a074 100644 --- a/tnc/data_handler.py +++ b/tnc/data_handler.py @@ -386,6 +386,28 @@ class DATA: while static.TRANSMITTING: time.sleep(0.01) + def send_data_to_socket_queue(self, /, **kwargs): + """ + Send information to the UI via JSON and the sock.SOCKET_QUEUE. + + Args: + Dictionary containing the data to be sent, in the format: + key=value, for each item. E.g.: + self.send_data_to_socket_queue( + arq="received", + uuid=uniqueid, + timestamp=timestamp, + mycallsign=str(mycallsign, "UTF-8"), + dxcallsign=str(static.DXCALLSIGN, "UTF-8"), + dxgrid=str(static.DXGRID, "UTF-8"), + data=base64_data, + ) + """ + jsondata = kwargs + self.log.debug("[TNC] send_data_to_socket_queue:", jsondata=jsondata) + json_data_out = json.dumps(jsondata) + sock.SOCKET_QUEUE.put(json_data_out) + def send_burst_ack_frame(self, snr) -> None: """Build and send ACK frame for burst DATA frame""" ack_frame = bytearray(14) @@ -720,18 +742,15 @@ class DATA: static.RX_BUFFER.append( [uniqueid, timestamp, static.DXCALLSIGN, static.DXGRID, base64_data] ) - jsondata = { - "arq": "received", - "uuid": uniqueid, - "timestamp": timestamp, - "mycallsign": str(mycallsign, "UTF-8"), - "dxcallsign": str(static.DXCALLSIGN, "UTF-8"), - "dxgrid": str(static.DXGRID, "UTF-8"), - "data": base64_data, - } - json_data_out = json.dumps(jsondata) - self.log.debug("[TNC] arq_data_received:", jsondata=jsondata) - sock.SOCKET_QUEUE.put(json_data_out) + self.send_data_to_socket_queue( + arq="received", + uuid=uniqueid, + timestamp=timestamp, + mycallsign=str(mycallsign, "UTF-8"), + dxcallsign=str(static.DXCALLSIGN, "UTF-8"), + dxgrid=str(static.DXGRID, "UTF-8"), + data=base64_data, + ) static.INFO.append("ARQ;RECEIVING;SUCCESS") self.log.info( @@ -807,15 +826,13 @@ class DATA: frame_total_size = len(data_out).to_bytes(4, byteorder="big") static.INFO.append("ARQ;TRANSMITTING") - jsondata = { - "arq": "transmission", - "status": "transmitting", - "uuid": self.transmission_uuid, - "percent": static.ARQ_TRANSMISSION_PERCENT, - "bytesperminute": static.ARQ_BYTES_PER_MINUTE, - } - json_data_out = json.dumps(jsondata) - sock.SOCKET_QUEUE.put(json_data_out) + self.send_data_to_socket_queue( + arq="transmission", + status="transmitting", + uuid=self.transmission_uuid, + percent=static.ARQ_TRANSMISSION_PERCENT, + bytesperminute=static.ARQ_BYTES_PER_MINUTE, + ) self.log.info("[TNC] | TX | DATACHANNEL", mode=mode, Bytes=static.TOTAL_BYTES) @@ -997,29 +1014,25 @@ class DATA: tx_start_of_transmission, bufferposition_end, len(data_out) ) - jsondata = { - "arq": "transmission", - "status": "transmitting", - "uuid": self.transmission_uuid, - "percent": static.ARQ_TRANSMISSION_PERCENT, - "bytesperminute": static.ARQ_BYTES_PER_MINUTE, - } - json_data_out = json.dumps(jsondata) - sock.SOCKET_QUEUE.put(json_data_out) + self.send_data_to_socket_queue( + arq="transmission", + status="transmitting", + uuid=self.transmission_uuid, + percent=static.ARQ_TRANSMISSION_PERCENT, + bytesperminute=static.ARQ_BYTES_PER_MINUTE, + ) # GOING TO NEXT ITERATION if self.data_frame_ack_received: + self.send_data_to_socket_queue( + arq="transmission", + status="success", + uuid=self.transmission_uuid, + percent=static.ARQ_TRANSMISSION_PERCENT, + bytesperminute=static.ARQ_BYTES_PER_MINUTE, + ) static.INFO.append("ARQ;TRANSMITTING;SUCCESS") - jsondata = { - "arq": "transmission", - "status": "success", - "uuid": self.transmission_uuid, - "percent": static.ARQ_TRANSMISSION_PERCENT, - "bytesperminute": static.ARQ_BYTES_PER_MINUTE, - } - json_data_out = json.dumps(jsondata) - sock.SOCKET_QUEUE.put(json_data_out) self.log.info( "[TNC] ARQ | TX | DATA TRANSMITTED!", @@ -1029,16 +1042,14 @@ class DATA: ) else: + self.send_data_to_socket_queue( + arq="transmission", + status="failed", + uuid=self.transmission_uuid, + percent=static.ARQ_TRANSMISSION_PERCENT, + bytesperminute=static.ARQ_BYTES_PER_MINUTE, + ) static.INFO.append("ARQ;TRANSMITTING;FAILED") - jsondata = { - "arq": "transmission", - "status": "failed", - "uuid": self.transmission_uuid, - "percent": static.ARQ_TRANSMISSION_PERCENT, - "bytesperminute": static.ARQ_BYTES_PER_MINUTE, - } - json_data_out = json.dumps(jsondata) - sock.SOCKET_QUEUE.put(json_data_out) self.log.info( "[TNC] ARQ | TX | TRANSMISSION FAILED OR TIME OUT!", @@ -1158,16 +1169,14 @@ class DATA: static.FREQ_OFFSET, static.HAMLIB_FREQUENCY, ) + self.send_data_to_socket_queue( + arq="transmission", + status="failed", + uuid=self.transmission_uuid, + percent=static.ARQ_TRANSMISSION_PERCENT, + bytesperminute=static.ARQ_BYTES_PER_MINUTE, + ) static.INFO.append("ARQ;TRANSMITTING;FAILED") - jsondata = { - "arq": "transmission", - "status": "failed", - "uuid": self.transmission_uuid, - "percent": static.ARQ_TRANSMISSION_PERCENT, - "bytesperminute": static.ARQ_BYTES_PER_MINUTE, - } - json_data_out = json.dumps(jsondata) - sock.SOCKET_QUEUE.put(json_data_out) # Update data_channel timestamp self.arq_session_last_received = int(time.time()) @@ -1553,15 +1562,13 @@ class DATA: "[TNC] arq_open_data_channel:", transmission_uuid=self.transmission_uuid ) - jsondata = { - "arq": "transmission", - "status": "failed", - "uuid": self.transmission_uuid, - "percent": static.ARQ_TRANSMISSION_PERCENT, - "bytesperminute": static.ARQ_BYTES_PER_MINUTE, - } - json_data_out = json.dumps(jsondata) - sock.SOCKET_QUEUE.put(json_data_out) + self.send_data_to_socket_queue( + arq="transmission", + status="failed", + uuid=self.transmission_uuid, + percent=static.ARQ_TRANSMISSION_PERCENT, + bytesperminute=static.ARQ_BYTES_PER_MINUTE, + ) self.log.warning( "[TNC] ARQ | TX | DATA [" @@ -1839,18 +1846,16 @@ class DATA: static.DXCALLSIGN_CRC = bytes(data_in[4:7]) static.DXGRID = bytes(data_in[7:13]).rstrip(b"\x00") - jsondata = { - "type": "ping", - "status": "ack", - "uuid": str(uuid.uuid4()), - "timestamp": int(time.time()), - "mycallsign": str(self.mycallsign, "UTF-8"), - "dxcallsign": str(static.DXCALLSIGN, "UTF-8"), - "dxgrid": str(static.DXGRID, "UTF-8"), - "snr": str(static.SNR), - } - json_data_out = json.dumps(jsondata) - sock.SOCKET_QUEUE.put(json_data_out) + self.send_data_to_socket_queue( + type="ping", + status="ack", + uuid=str(uuid.uuid4()), + timestamp=int(time.time()), + mycallsign=str(self.mycallsign, "UTF-8"), + dxcallsign=str(static.DXCALLSIGN, "UTF-8"), + dxgrid=str(static.DXGRID, "UTF-8"), + snr=str(static.SNR), + ) helpers.add_to_heard_stations( static.DXCALLSIGN, @@ -1962,20 +1967,18 @@ class DATA: dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7])) dxgrid = bytes(data_in[9:13]).rstrip(b"\x00") - jsondata = { - "type": "beacon", - "status": "received", - "uuid": str(uuid.uuid4()), - "timestamp": int(time.time()), - "mycallsign": str(self.mycallsign, "UTF-8"), - "dxcallsign": str(dxcallsign, "UTF-8"), - "dxgrid": str(dxgrid, "UTF-8"), - "snr": str(static.SNR), - } - json_data_out = json.dumps(jsondata) - sock.SOCKET_QUEUE.put(json_data_out) - + self.send_data_to_socket_queue( + type="beacon", + status="received", + uuid=str(uuid.uuid4()), + timestamp=int(time.time()), + mycallsign=str(self.mycallsign, "UTF-8"), + dxcallsign=str(dxcallsign, "UTF-8"), + dxgrid=str(dxgrid, "UTF-8"), + snr=str(static.SNR), + ) static.INFO.append("BEACON;RECEIVING") + self.log.info( "[TNC] BEACON RCVD [" + str(dxcallsign, "UTF-8") @@ -2090,20 +2093,18 @@ class DATA: dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7])) dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8") - jsondata = { - "type": "qrv", - "status": "received", - "uuid": str(uuid.uuid4()), - "timestamp": int(time.time()), - "mycallsign": str(self.mycallsign, "UTF-8"), - "dxcallsign": str(dxcallsign, "UTF-8"), - "dxgrid": str(dxgrid, "UTF-8"), - "snr": str(static.SNR), - } - json_data_out = json.dumps(jsondata) - sock.SOCKET_QUEUE.put(json_data_out) - + self.send_data_to_socket_queue( + type="qrv", + status="received", + uuid=str(uuid.uuid4()), + timestamp=int(time.time()), + mycallsign=str(self.mycallsign, "UTF-8"), + dxcallsign=str(dxcallsign, "UTF-8"), + dxgrid=str(dxgrid, "UTF-8"), + snr=str(static.SNR), + ) static.INFO.append("QRV;RECEIVING") + self.log.info( "[TNC] QRV RCVD [" + str(dxcallsign, "UTF-8") From 13fca45fc1f017f2e37a150d61e0d2f79568c052 Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Mon, 30 May 2022 13:47:51 -0400 Subject: [PATCH 18/26] Format tnc directory with black. --- tnc/daemon.py | 1 + tnc/main.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tnc/daemon.py b/tnc/daemon.py index fb799739..161bbdc9 100755 --- a/tnc/daemon.py +++ b/tnc/daemon.py @@ -55,6 +55,7 @@ class DAEMON: Daemon class """ + log = structlog.get_logger("DAEMON") def __init__(self): diff --git a/tnc/main.py b/tnc/main.py index 642713ea..4eec6e86 100755 --- a/tnc/main.py +++ b/tnc/main.py @@ -25,6 +25,7 @@ import structlog log = structlog.get_logger(__file__) + def signal_handler(sig, frame): """ a signal handler, which closes the network/socket when closing the application From 800099ab62d4c295c1ace5734a3fba5e0b60ff69 Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Mon, 30 May 2022 14:48:46 -0400 Subject: [PATCH 19/26] Simplify option handling. --- tnc/daemon.py | 57 +++++++++++++++++---------------------------------- 1 file changed, 19 insertions(+), 38 deletions(-) diff --git a/tnc/daemon.py b/tnc/daemon.py index 161bbdc9..01d3feb0 100755 --- a/tnc/daemon.py +++ b/tnc/daemon.py @@ -158,59 +158,43 @@ class DAEMON: # list of parameters, necessary for running subprocess command as a list options = [] - options.append("--port") - options.append(str(static.DAEMONPORT - 1)) + options.append(("--port", str(static.DAEMONPORT - 1))) - options.append("--mycall") - options.append(data[1]) + options.append(("--mycall", data[1])) - options.append("--mygrid") - options.append(data[2]) + options.append(("--mygrid", data[2])) - options.append("--rx") - options.append(data[3]) + options.append(("--rx", data[3])) - options.append("--tx") - options.append(data[4]) + options.append(("--tx", data[4])) # if radiocontrol != disabled # this should hopefully avoid a ton of problems if we are just running in # disabled mode if data[13] != "disabled": - options.append("--devicename") - options.append(data[5]) + options.append(("--devicename", data[5])) - options.append("--deviceport") - options.append(data[6]) + options.append(("--deviceport", data[6])) - options.append("--serialspeed") - options.append(data[7]) + options.append(("--serialspeed", data[7])) - options.append("--pttprotocol") - options.append(data[8]) + options.append(("--pttprotocol", data[8])) - options.append("--pttport") - options.append(data[9]) + options.append(("--pttport", data[9])) - options.append("--data_bits") - options.append(data[10]) + options.append(("--data_bits", data[10])) - options.append("--stop_bits") - options.append(data[11]) + options.append(("--stop_bits", data[11])) - options.append("--handshake") - options.append(data[12]) + options.append(("--handshake", data[12])) - options.append("--radiocontrol") - options.append(data[13]) + options.append(("--radiocontrol", data[13])) if data[13] == "rigctld": - options.append("--rigctld_ip") - options.append(data[14]) + options.append(("--rigctld_ip", data[14])) - options.append("--rigctld_port") - options.append(data[15]) + options.append(("--rigctld_port", data[15])) if data[16] == "True": options.append("--scatter") @@ -221,18 +205,15 @@ class DAEMON: if data[18] == "True": options.append("--500hz") - options.append("--tuning_range_fmin") - options.append(data[19]) + options.append(("--tuning_range_fmin", data[19])) - options.append("--tuning_range_fmax") - options.append(data[20]) + options.append(("--tuning_range_fmax", data[20])) # overriding FSK mode # if data[21] == "True": # options.append("--fsk") - options.append("--tx-audio-level") - options.append(data[22]) + options.append(("--tx-audio-level", data[22])) if data[23] == "True": options.append("--qrv") From 4af1280e0a2532d2462c95bb9a4009a840affe1f Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Mon, 30 May 2022 16:01:25 -0400 Subject: [PATCH 20/26] Remove unreachable code. --- tnc/daemon.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tnc/daemon.py b/tnc/daemon.py index 01d3feb0..f4b6b31f 100755 --- a/tnc/daemon.py +++ b/tnc/daemon.py @@ -314,12 +314,9 @@ class DAEMON: if pttstate: self.log.info("[DMN] Hamlib PTT", status="SUCCESS") response = {"command": "test_hamlib", "result": "SUCCESS"} - elif not pttstate: + else: self.log.warning("[DMN] Hamlib PTT", status="NO SUCCESS") response = {"command": "test_hamlib", "result": "NOSUCCESS"} - else: - self.log.error("[DMN] Hamlib PTT", status="FAILED") - response = {"command": "test_hamlib", "result": "FAILED"} hamlib.set_ptt(False) hamlib.close_rig() From d04a306be88d9d55dc9c5ac3a20f8c99009f6dce Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Mon, 30 May 2022 16:33:36 -0400 Subject: [PATCH 21/26] Revert "Simplify option handling." This reverts commit 800099ab62d4c295c1ace5734a3fba5e0b60ff69. --- tnc/daemon.py | 57 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/tnc/daemon.py b/tnc/daemon.py index f4b6b31f..89452384 100755 --- a/tnc/daemon.py +++ b/tnc/daemon.py @@ -158,43 +158,59 @@ class DAEMON: # list of parameters, necessary for running subprocess command as a list options = [] - options.append(("--port", str(static.DAEMONPORT - 1))) + options.append("--port") + options.append(str(static.DAEMONPORT - 1)) - options.append(("--mycall", data[1])) + options.append("--mycall") + options.append(data[1]) - options.append(("--mygrid", data[2])) + options.append("--mygrid") + options.append(data[2]) - options.append(("--rx", data[3])) + options.append("--rx") + options.append(data[3]) - options.append(("--tx", data[4])) + options.append("--tx") + options.append(data[4]) # if radiocontrol != disabled # this should hopefully avoid a ton of problems if we are just running in # disabled mode if data[13] != "disabled": - options.append(("--devicename", data[5])) + options.append("--devicename") + options.append(data[5]) - options.append(("--deviceport", data[6])) + options.append("--deviceport") + options.append(data[6]) - options.append(("--serialspeed", data[7])) + options.append("--serialspeed") + options.append(data[7]) - options.append(("--pttprotocol", data[8])) + options.append("--pttprotocol") + options.append(data[8]) - options.append(("--pttport", data[9])) + options.append("--pttport") + options.append(data[9]) - options.append(("--data_bits", data[10])) + options.append("--data_bits") + options.append(data[10]) - options.append(("--stop_bits", data[11])) + options.append("--stop_bits") + options.append(data[11]) - options.append(("--handshake", data[12])) + options.append("--handshake") + options.append(data[12]) - options.append(("--radiocontrol", data[13])) + options.append("--radiocontrol") + options.append(data[13]) if data[13] == "rigctld": - options.append(("--rigctld_ip", data[14])) + options.append("--rigctld_ip") + options.append(data[14]) - options.append(("--rigctld_port", data[15])) + options.append("--rigctld_port") + options.append(data[15]) if data[16] == "True": options.append("--scatter") @@ -205,15 +221,18 @@ class DAEMON: if data[18] == "True": options.append("--500hz") - options.append(("--tuning_range_fmin", data[19])) + options.append("--tuning_range_fmin") + options.append(data[19]) - options.append(("--tuning_range_fmax", data[20])) + options.append("--tuning_range_fmax") + options.append(data[20]) # overriding FSK mode # if data[21] == "True": # options.append("--fsk") - options.append(("--tx-audio-level", data[22])) + options.append("--tx-audio-level") + options.append(data[22]) if data[23] == "True": options.append("--qrv") From 8cc1e721b63808b0febbc219a7a7247aa543b709 Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Mon, 30 May 2022 16:40:58 -0400 Subject: [PATCH 22/26] Make argument name more useful. Also fix a missed bandwith -> bandwidth typo fix. --- tnc/data_handler.py | 3 +-- tnc/main.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tnc/data_handler.py b/tnc/data_handler.py index c076a074..92d8b5cd 100644 --- a/tnc/data_handler.py +++ b/tnc/data_handler.py @@ -386,7 +386,7 @@ class DATA: while static.TRANSMITTING: time.sleep(0.01) - def send_data_to_socket_queue(self, /, **kwargs): + def send_data_to_socket_queue(self, /, **jsondata): """ Send information to the UI via JSON and the sock.SOCKET_QUEUE. @@ -403,7 +403,6 @@ class DATA: data=base64_data, ) """ - jsondata = kwargs self.log.debug("[TNC] send_data_to_socket_queue:", jsondata=jsondata) json_data_out = json.dumps(jsondata) sock.SOCKET_QUEUE.put(json_data_out) diff --git a/tnc/main.py b/tnc/main.py index 4eec6e86..6ca65012 100755 --- a/tnc/main.py +++ b/tnc/main.py @@ -185,9 +185,9 @@ if __name__ == "__main__": ) PARSER.add_argument( "--500hz", - dest="low_bandwith_mode", + dest="low_bandwidth_mode", action="store_true", - help="Enable low bandwith mode ( 500 Hz only )", + help="Enable low bandwidth mode ( 500 Hz only )", ) PARSER.add_argument( "--fsk", From eeb8532651c64cfbe75c5c9d497bcbcc7e580e27 Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Tue, 31 May 2022 19:45:25 -0400 Subject: [PATCH 23/26] Change modem from PR comments. --- tnc/modem.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tnc/modem.py b/tnc/modem.py index e091eb4d..316bc8f7 100644 --- a/tnc/modem.py +++ b/tnc/modem.py @@ -59,7 +59,6 @@ class RF: self.AUDIO_FRAMES_PER_BUFFER_TX = 2400 * 2 # 8 * (self.AUDIO_SAMPLE_RATE_RX/self.MODEM_SAMPLE_RATE) == 48 - self.AUDIO_CHUNKS = 48 self.AUDIO_CHANNELS = 1 self.MODE = 0 @@ -923,18 +922,18 @@ def get_bytes_per_frame(mode: int) -> int: def set_audio_volume(datalist, volume: float) -> np.int16: """ Scale values for the provided audio samples by volume, - `volume` is clipped to the range of 0-100 + `volume` is clipped to the range of 0-200 :param datalist: Audio samples to scale :type datalist: NDArray[np.int16] - :param volume: Percentage (0-100) to scale samples + :param volume: "Percentage" (0-200) to scale samples :type volume: float :return: Scaled audio samples :rtype: np.int16 """ # Clip volume provided to acceptable values volume = min(volume, 0.0) - volume = max(volume, 100.0) + volume = max(volume, 200.0) # Scale samples by the ratio of volume / 100.0 data = np.fromstring(datalist, np.int16) * (volume / 100.0) # type: ignore From 2be89b5b2092956dbb79fa151dfcb7f58919823b Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Tue, 31 May 2022 19:45:37 -0400 Subject: [PATCH 24/26] Change sock per PR comments. --- tnc/sock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tnc/sock.py b/tnc/sock.py index f67289b7..3becae85 100644 --- a/tnc/sock.py +++ b/tnc/sock.py @@ -214,7 +214,7 @@ def process_tnc_commands(data): "[SCK] command execution error", e=err, command=received_json ) - # TRANSMIT SINE WAVE ----------------------------------------------------- + # TRANSMIT TEST FRAME ---------------------------------------------------- if ( received_json["type"] == "set" and received_json["command"] == "send_test_frame" From b44c60fa32823dfa39887fd3655f0e05416f45fd Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Tue, 31 May 2022 20:34:22 -0400 Subject: [PATCH 25/26] Add flexibility to log_handler. --- tnc/log_handler.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tnc/log_handler.py b/tnc/log_handler.py index bda36caf..514c2086 100644 --- a/tnc/log_handler.py +++ b/tnc/log_handler.py @@ -4,13 +4,13 @@ import structlog # https://www.structlog.org/en/stable/standard-library.html -def setup_logging(filename): +def setup_logging(filename: str = "", level: str = "DEBUG"): """ Args: filename: - - Returns: + level:str: Log level to output, possible values are: + "CRITICAL", "FATAL", "ERROR", "WARNING", "WARN", "INFO", "DEBUG" """ @@ -40,12 +40,12 @@ def setup_logging(filename): }, "handlers": { "default": { - "level": "DEBUG", + "level": level, "class": "logging.StreamHandler", "formatter": "colored", }, "file": { - "level": "DEBUG", + "level": level, "class": "logging.handlers.WatchedFileHandler", "filename": f"{filename}.log", "formatter": "plain", @@ -53,8 +53,8 @@ def setup_logging(filename): }, "loggers": { "": { - "handlers": ["default", "file"], - "level": "DEBUG", + "handlers": ["default", "file"] if filename else ["default"], + "level": level, "propagate": True, }, }, From 647fd2d6ca27658dd91fdeccfafe1645afd45ebe Mon Sep 17 00:00:00 2001 From: Paul Kronenwetter Date: Tue, 31 May 2022 20:35:35 -0400 Subject: [PATCH 26/26] Adjust get_logger names. --- tnc/codec2.py | 2 +- tnc/helpers.py | 2 +- tnc/main.py | 2 +- tnc/rig.py | 2 +- tnc/rigctl.py | 2 +- tnc/rigctld.py | 2 +- tnc/sock.py | 6 +++--- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tnc/codec2.py b/tnc/codec2.py index 78244dba..d8cd5a1d 100644 --- a/tnc/codec2.py +++ b/tnc/codec2.py @@ -16,7 +16,7 @@ from threading import Lock import numpy as np import structlog -log = structlog.get_logger(__file__) +log = structlog.get_logger("codec2") # Enum for codec2 modes class FREEDV_MODE(Enum): diff --git a/tnc/helpers.py b/tnc/helpers.py index c5d8ace2..2b6ffa45 100644 --- a/tnc/helpers.py +++ b/tnc/helpers.py @@ -10,7 +10,7 @@ import crcengine import static import structlog -log = structlog.get_logger(__file__) +log = structlog.get_logger("helpers") def wait(seconds: float) -> bool: diff --git a/tnc/main.py b/tnc/main.py index 6ca65012..06e2ca46 100755 --- a/tnc/main.py +++ b/tnc/main.py @@ -23,7 +23,7 @@ import modem import static import structlog -log = structlog.get_logger(__file__) +log = structlog.get_logger("main") def signal_handler(sig, frame): diff --git a/tnc/rig.py b/tnc/rig.py index fa30f32e..2b1b5517 100644 --- a/tnc/rig.py +++ b/tnc/rig.py @@ -6,7 +6,7 @@ import sys import structlog -mainlog = structlog.get_logger(__file__) +mainlog = structlog.get_logger("rig") # set global hamlib version hamlib_version = 0 diff --git a/tnc/rigctl.py b/tnc/rigctl.py index 1a5eff2e..dd6e202b 100644 --- a/tnc/rigctl.py +++ b/tnc/rigctl.py @@ -22,7 +22,7 @@ hamlib_version = 0 class radio: """ """ - log = structlog.get_logger(__name__) + log = structlog.get_logger("radio (rigctl)") def __init__(self): self.devicename = "" diff --git a/tnc/rigctld.py b/tnc/rigctld.py index 0d55e01d..076f3a93 100644 --- a/tnc/rigctld.py +++ b/tnc/rigctld.py @@ -18,7 +18,7 @@ class radio: # Note: This is a massive hack. - log = structlog.get_logger(__name__) + log = structlog.get_logger("radio (rigctld)") def __init__(self, hostname="localhost", port=4532, poll_rate=5, timeout=5): """Open a connection to rigctld, and test it for validity""" diff --git a/tnc/sock.py b/tnc/sock.py index 3becae85..a71ffc49 100644 --- a/tnc/sock.py +++ b/tnc/sock.py @@ -50,7 +50,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): """ """ connection_alive = False - log = structlog.get_logger(__name__) + log = structlog.get_logger("ThreadedTCPRequestHandler") def send_to_client(self): """ @@ -505,7 +505,7 @@ def process_daemon_commands(data): Returns: """ - log = structlog.get_logger(__name__) + log = structlog.get_logger("process_daemon_commands") # convert data to json object received_json = json.loads(data) @@ -675,7 +675,7 @@ def send_daemon_state(): """ send the daemon state to network """ - log = structlog.get_logger(__name__) + log = structlog.get_logger("send_daemon_state") try: python_version = f"{str(sys.version_info[0])}.{str(sys.version_info[1])}"