FreeDATA/tnc/sock.py

1068 lines
40 KiB
Python
Raw Normal View History

2021-02-16 13:23:57 +00:00
# -*- coding: utf-8 -*-
"""
Created on Fri Dec 25 21:25:14 2020
@author: DJ2LS
2021-08-08 09:08:34 +00:00
# GET COMMANDS
# "command" : "..."
2022-05-09 00:41:49 +00:00
# SET COMMANDS
# "command" : "..."
# "parameter" : " ..."
2021-08-08 09:08:34 +00:00
# DATA COMMANDS
# "command" : "..."
# "type" : "..."
# "dxcallsign" : "..."
# "data" : "..."
2021-02-16 13:23:57 +00:00
"""
import atexit
import base64
import queue
2021-02-16 13:23:57 +00:00
import socketserver
import sys
2021-02-16 13:23:57 +00:00
import threading
2021-03-17 10:22:06 +00:00
import time
2022-12-27 20:13:08 +00:00
import wave
import helpers
import static
import structlog
from random import randrange
import ujson as json
2022-06-17 23:48:47 +00:00
from exceptions import NoCallsign
from queues import DATA_QUEUE_TRANSMIT, RX_BUFFER, RIGCTLD_COMMAND_QUEUE
2021-11-18 18:40:22 +00:00
SOCKET_QUEUE = queue.Queue()
DAEMON_QUEUE = queue.Queue()
CONNECTED_CLIENTS = set()
CLOSE_SIGNAL = False
2023-02-10 09:15:10 +00:00
TESTMODE = False
log = structlog.get_logger("sock")
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
"""
the socket handler base class
"""
pass
2022-05-09 00:41:49 +00:00
2023-02-10 11:12:18 +00:00
# noinspection PyTypeChecker
class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
""" """
connection_alive = False
2022-06-01 00:35:35 +00:00
log = structlog.get_logger("ThreadedTCPRequestHandler")
def send_to_client(self):
"""
function called by socket handler
send data to a network client if available
"""
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
if self.server.server_address[1] == static.PORT and not static.TNCSTARTED:
data = send_tnc_state()
if data != tempdata:
tempdata = data
SOCKET_QUEUE.put(data)
else:
data = send_daemon_state()
if data != tempdata:
tempdata = data
SOCKET_QUEUE.put(data)
threading.Event().wait(0.5)
2022-05-09 00:41:49 +00:00
while not SOCKET_QUEUE.empty():
data = SOCKET_QUEUE.get()
sock_data = bytes(data, "utf-8")
sock_data += b"\n" # append line limiter
2022-05-09 00:41:49 +00:00
# send data to all clients
try:
for client in CONNECTED_CLIENTS:
try:
client.send(sock_data)
except Exception as err:
self.log.info("[SCK] Connection lost", e=err)
2023-02-10 11:12:18 +00:00
# TODO: Check if we really should set connection alive to false.
# This might disconnect all other clients as well...
self.connection_alive = False
except Exception as err:
self.log.debug("[SCK] catch harmless RuntimeError: Set changed size during iteration", e=err)
# we want to transmit scatter data only once to reduce network traffic
static.SCATTER = []
# we want to display INFO messages only once
2022-05-09 00:41:49 +00:00
static.INFO = []
# self.request.sendall(sock_data)
threading.Event().wait(0.15)
def receive_from_client(self):
"""
function which is called by the socket handler
it processes the data which is returned by a client
"""
data = bytes()
while self.connection_alive and not CLOSE_SIGNAL:
try:
chunk = self.request.recv(1024)
data += chunk
2022-05-09 00:41:49 +00:00
if chunk == b"":
# print("connection broken. Closing...")
self.connection_alive = False
2022-03-06 16:23:04 +00:00
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")
# remove empty data
data.remove(b"")
2022-05-09 00:41:49 +00:00
# iterate thorugh data list
for commands in data:
if self.server.server_address[1] == static.PORT:
2023-02-09 20:43:49 +00:00
self.process_tnc_commands(commands)
else:
2023-02-09 20:43:49 +00:00
self.process_daemon_commands(commands)
# wait some time between processing multiple commands
# this is only a first test to avoid doubled transmission
2022-05-09 00:41:49 +00:00
# we might improve this by only processing one command or
2023-02-10 11:12:18 +00:00
# doing some kind of selection to determine which commands need to be dropped
# and which one can be processed during a running transmission
2023-01-11 15:18:29 +00:00
threading.Event().wait(0.5)
2022-05-09 00:41:49 +00:00
# finally delete our rx buffer to be ready for new commands
data = bytes()
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
2022-05-09 00:41:49 +00:00
def handle(self):
"""
socket handler
"""
CONNECTED_CLIENTS.add(self.request)
self.log.debug(
"[SCK] Client connected",
ip=self.client_address[0],
port=self.client_address[1],
)
self.connection_alive = True
2022-05-09 00:41:49 +00:00
self.sendThread = threading.Thread(
target=self.send_to_client, args=[], daemon=True
)
self.sendThread.start()
self.receiveThread = threading.Thread(
target=self.receive_from_client, args=[], daemon=True
)
self.receiveThread.start()
2022-05-09 00:41:49 +00:00
# keep connection alive until we close it
while self.connection_alive and not CLOSE_SIGNAL:
threading.Event().wait(1)
def finish(self):
""" """
self.log.warning(
"[SCK] Closing client socket",
ip=self.client_address[0],
port=self.client_address[1],
)
try:
CONNECTED_CLIENTS.remove(self.request)
2023-02-10 11:12:18 +00:00
except Exception as e:
self.log.warning(
"[SCK] client connection already removed from client list",
client=self.request,
2023-02-10 11:12:18 +00:00
e=e,
)
2023-02-09 20:43:49 +00:00
# ------------------------ TNC COMMANDS
def process_tnc_commands(self, data):
"""
process tnc commands
2023-02-09 20:43:49 +00:00
Args:
data:
2022-12-04 15:56:12 +00:00
2023-02-09 20:43:49 +00:00
Returns:
2022-12-04 15:56:12 +00:00
2023-02-09 20:43:49 +00:00
"""
log = structlog.get_logger("process_tnc_commands")
2023-02-09 20:43:49 +00:00
# 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)
log.debug("[SCK] CMD", command=received_json)
# ENABLE TNC LISTENING STATE
if received_json["type"] == "set" and received_json["command"] == "listen":
2023-02-10 09:15:10 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_set_listen(None, received_json)
else:
self.tnc_set_listen(received_json)
2023-02-09 20:43:49 +00:00
# START STOP AUDIO RECORDING
if received_json["type"] == "set" and received_json["command"] == "record_audio":
2023-02-10 09:15:10 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_set_record_audio(None, received_json)
else:
self.tnc_set_record_audio(received_json)
2023-02-09 20:43:49 +00:00
# SET ENABLE/DISABLE RESPOND TO CALL
if received_json["type"] == "set" and received_json["command"] == "respond_to_call":
2023-02-10 09:15:10 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_set_respond_to_call(None, received_json)
else:
self.tnc_set_respond_to_call(received_json)
2023-02-09 20:43:49 +00:00
# SET ENABLE RESPOND TO CQ
if received_json["type"] == "set" and received_json["command"] == "respond_to_cq":
2023-02-10 09:15:10 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_set_record_audio(None, received_json)
else:
self.tnc_set_record_audio(received_json)
2023-02-09 20:43:49 +00:00
# SET TX AUDIO LEVEL
if received_json["type"] == "set" and received_json["command"] == "tx_audio_level":
2023-02-10 09:15:10 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_set_tx_audio_level(None, received_json)
else:
self.tnc_set_tx_audio_level(received_json)
2023-02-09 20:43:49 +00:00
# TRANSMIT TEST FRAME
if received_json["type"] == "set" and received_json["command"] == "send_test_frame":
2023-02-10 09:15:10 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_set_send_test_frame(None, received_json)
else:
self.tnc_set_send_test_frame(received_json)
2023-02-09 20:43:49 +00:00
2023-02-12 17:24:42 +00:00
# TRANSMIT FEC FRAME
2023-02-12 16:39:13 +00:00
if received_json["type"] == "fec" and received_json["command"] == "transmit":
if TESTMODE:
ThreadedTCPRequestHandler.tnc_fec_transmit(None, received_json)
else:
self.tnc_fec_transmit(received_json)
2023-02-12 17:24:42 +00:00
# TRANSMIT IS WRITING FRAME
if received_json["type"] == "fec" and received_json["command"] == "transmit_is_writing":
if TESTMODE:
ThreadedTCPRequestHandler.tnc_fec_is_writing(None, received_json)
else:
self.tnc_fec_is_writing(received_json)
2023-02-09 20:43:49 +00:00
# CQ CQ CQ
if received_json["command"] == "cqcqcq":
2023-02-10 09:31:33 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_cqcqcq(None, received_json)
else:
self.tnc_cqcqcq(received_json)
2023-02-09 20:43:49 +00:00
# START_BEACON
if received_json["command"] == "start_beacon":
2023-02-10 09:15:10 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_start_beacon(None, received_json)
else:
self.tnc_start_beacon(received_json)
2023-02-09 20:43:49 +00:00
# STOP_BEACON
if received_json["command"] == "stop_beacon":
2023-02-10 09:15:10 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_stop_beacon(None, received_json)
else:
self.tnc_stop_beacon(received_json)
2023-02-09 20:43:49 +00:00
# PING
if received_json["type"] == "ping" and received_json["command"] == "ping":
2023-02-10 09:15:10 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_ping_ping(None, received_json)
else:
self.tnc_ping_ping(received_json)
2023-02-09 20:43:49 +00:00
# CONNECT
if received_json["type"] == "arq" and received_json["command"] == "connect":
2023-02-10 09:15:10 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_arq_connect(None, received_json)
else:
self.tnc_arq_connect(received_json)
2023-02-09 20:43:49 +00:00
# DISCONNECT
if received_json["type"] == "arq" and received_json["command"] == "disconnect":
2023-02-10 09:15:10 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_arq_disconnect(None, received_json)
else:
self.tnc_arq_disconnect(received_json)
2023-02-09 20:43:49 +00:00
# TRANSMIT RAW DATA
if received_json["type"] == "arq" and received_json["command"] == "send_raw":
2023-02-10 09:15:10 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_arq_send_raw(None, received_json)
else:
self.tnc_arq_send_raw(received_json)
2023-02-09 20:43:49 +00:00
# STOP TRANSMISSION
if received_json["type"] == "arq" and received_json["command"] == "stop_transmission":
2023-02-10 09:15:10 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_arq_stop_transmission(None, received_json)
else:
2023-02-10 11:12:18 +00:00
self.tnc_arq_stop_transmission(received_json)
2023-02-10 09:15:10 +00:00
2023-02-09 20:43:49 +00:00
# GET RX BUFFER
if received_json["type"] == "get" and received_json["command"] == "rx_buffer":
2023-02-10 09:15:10 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_get_rx_buffer(None, received_json)
else:
self.tnc_get_rx_buffer(received_json)
2023-02-09 20:43:49 +00:00
# DELETE RX BUFFER
if received_json["type"] == "set" and received_json["command"] == "del_rx_buffer":
2023-02-10 09:15:10 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_set_del_rx_buffer(None, received_json)
else:
self.tnc_set_del_rx_buffer(received_json)
2023-02-09 20:43:49 +00:00
# SET FREQUENCY
if received_json["type"] == "set" and received_json["command"] == "frequency":
2023-02-10 09:15:10 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_set_frequency(None, received_json)
else:
self.tnc_set_frequency(received_json)
2023-02-09 20:43:49 +00:00
# SET MODE
if received_json["type"] == "set" and received_json["command"] == "mode":
2023-02-10 09:15:10 +00:00
if TESTMODE:
ThreadedTCPRequestHandler.tnc_set_mode(None, received_json)
else:
self.tnc_set_mode(received_json)
2023-02-09 20:43:49 +00:00
except Exception as err:
log.error("[SCK] JSON decoding error", e=err)
2023-02-10 09:15:10 +00:00
2023-02-09 20:43:49 +00:00
def tnc_set_listen(self, received_json):
try:
static.LISTEN = received_json["state"] in ['true', 'True', True, "ON", "on"]
command_response("listen", True)
2022-12-01 09:05:24 +00:00
2023-02-09 20:43:49 +00:00
# if tnc is connected, force disconnect when static.LISTEN == False
if not static.LISTEN and static.ARQ_SESSION_STATE not in ["disconnecting", "disconnected", "failed"]:
DATA_QUEUE_TRANSMIT.put(["DISCONNECT"])
# set early disconnecting state so we can interrupt connection attempts
static.ARQ_SESSION_STATE = "disconnecting"
command_response("disconnect", True)
2022-12-01 09:05:24 +00:00
2023-02-09 20:43:49 +00:00
except Exception as err:
command_response("listen", False)
log.warning(
"[SCK] CQ command execution error", e=err, command=received_json
)
2023-02-10 11:12:18 +00:00
2023-02-09 20:43:49 +00:00
def tnc_set_record_audio(self, received_json):
try:
if not static.AUDIO_RECORD:
static.AUDIO_RECORD_FILE = wave.open(f"{int(time.time())}_audio_recording.wav", 'w')
static.AUDIO_RECORD_FILE.setnchannels(1)
static.AUDIO_RECORD_FILE.setsampwidth(2)
static.AUDIO_RECORD_FILE.setframerate(8000)
static.AUDIO_RECORD = True
else:
static.AUDIO_RECORD = False
static.AUDIO_RECORD_FILE.close()
2022-03-31 19:13:30 +00:00
2023-02-09 20:43:49 +00:00
command_response("respond_to_call", True)
2022-05-09 00:41:49 +00:00
2023-02-09 20:43:49 +00:00
except Exception as err:
command_response("respond_to_call", False)
log.warning(
"[SCK] CQ command execution error", e=err, command=received_json
)
2022-05-09 01:27:24 +00:00
2023-02-09 20:43:49 +00:00
def tnc_set_respond_to_call(self, received_json):
try:
static.RESPOND_TO_CALL = received_json["state"] in ['true', 'True', True]
command_response("respond_to_call", True)
2022-05-09 00:41:49 +00:00
2023-02-09 20:43:49 +00:00
except Exception as err:
command_response("respond_to_call", False)
log.warning(
"[SCK] CQ command execution error", e=err, command=received_json
)
2022-05-09 00:41:49 +00:00
2023-02-09 20:43:49 +00:00
def tnc_set_respond_to_cq(self, received_json):
try:
static.RESPOND_TO_CQ = received_json["state"] in ['true', 'True', True]
command_response("respond_to_cq", True)
2023-02-09 20:43:49 +00:00
except Exception as err:
command_response("respond_to_cq", False)
log.warning(
"[SCK] CQ command execution error", e=err, command=received_json
)
2022-05-09 00:41:49 +00:00
2023-02-09 20:43:49 +00:00
def tnc_set_tx_audio_level(self, received_json):
try:
static.TX_AUDIO_LEVEL = int(received_json["value"])
command_response("tx_audio_level", True)
2022-05-09 00:41:49 +00:00
2023-02-09 20:43:49 +00:00
except Exception as err:
command_response("tx_audio_level", False)
log.warning(
"[SCK] TX audio command execution error",
e=err,
command=received_json,
)
2023-02-09 20:43:49 +00:00
def tnc_set_send_test_frame(self, received_json):
try:
DATA_QUEUE_TRANSMIT.put(["SEND_TEST_FRAME"])
command_response("send_test_frame", True)
except Exception as err:
command_response("send_test_frame", False)
log.warning(
"[SCK] Send test frame command execution error",
e=err,
command=received_json,
)
2023-02-10 11:12:18 +00:00
2023-02-12 16:39:13 +00:00
def tnc_fec_transmit(self, received_json):
try:
mode = received_json["mode"]
2023-02-12 16:48:01 +00:00
base64data = received_json["payload"]
if len(base64data) % 4:
raise TypeError
payload = base64.b64decode(base64data)
2023-02-12 16:39:13 +00:00
DATA_QUEUE_TRANSMIT.put(["FEC", payload, mode])
command_response("fec_transmit", True)
except Exception as err:
command_response("fec_transmit", False)
log.warning(
"[SCK] Send fec frame command execution error",
e=err,
command=received_json,
)
2023-02-12 17:24:42 +00:00
def tnc_fec_is_writing(self, received_json):
try:
mycallsign = received_json["mycallsign"]
DATA_QUEUE_TRANSMIT.put(["FEC_IS_WRITING", mycallsign])
command_response("fec_is_writing", True)
except Exception as err:
command_response("fec_is_writing", False)
log.warning(
"[SCK] Send fec frame command execution error",
e=err,
command=received_json,
)
2023-02-09 20:43:49 +00:00
def tnc_cqcqcq(self, received_json):
try:
DATA_QUEUE_TRANSMIT.put(["CQ"])
command_response("cqcqcq", True)
2023-02-09 20:43:49 +00:00
except Exception as err:
command_response("cqcqcq", False)
log.warning(
"[SCK] CQ command execution error", e=err, command=received_json
)
2023-02-09 20:43:49 +00:00
def tnc_start_beacon(self, received_json):
try:
static.BEACON_STATE = True
interval = int(received_json["parameter"])
DATA_QUEUE_TRANSMIT.put(["BEACON", interval, True])
command_response("start_beacon", True)
except Exception as err:
command_response("start_beacon", False)
log.warning(
"[SCK] Start beacon command execution error",
e=err,
command=received_json,
)
2023-02-10 11:12:18 +00:00
2023-02-09 20:43:49 +00:00
def tnc_stop_beacon(self, received_json):
try:
log.warning("[SCK] Stopping beacon!")
static.BEACON_STATE = False
DATA_QUEUE_TRANSMIT.put(["BEACON", None, False])
command_response("stop_beacon", True)
except Exception as err:
command_response("stop_beacon", False)
log.warning(
"[SCK] Stop beacon command execution error",
e=err,
command=received_json,
)
2023-02-10 11:12:18 +00:00
2023-02-09 20:43:49 +00:00
def tnc_ping_ping(self, received_json):
# send ping frame and wait for ACK
try:
dxcallsign = received_json["dxcallsign"]
2023-02-09 20:43:49 +00:00
if not str(dxcallsign).strip():
raise NoCallsign
# additional step for being sure our callsign is correctly
# in case we are not getting a station ssid
# then we are forcing a station ssid = 0
dxcallsign = helpers.callsign_to_bytes(dxcallsign)
dxcallsign = helpers.bytes_to_callsign(dxcallsign)
2022-05-09 00:41:49 +00:00
# check if specific callsign is set with different SSID than the TNC is initialized
try:
2022-12-05 15:57:45 +00:00
mycallsign = received_json["mycallsign"]
mycallsign = helpers.callsign_to_bytes(mycallsign)
mycallsign = helpers.bytes_to_callsign(mycallsign)
except Exception:
mycallsign = static.MYCALLSIGN
2023-02-09 20:43:49 +00:00
DATA_QUEUE_TRANSMIT.put(["PING", mycallsign, dxcallsign])
command_response("ping", True)
except NoCallsign:
command_response("ping", False)
log.warning("[SCK] callsign required for ping", command=received_json)
except Exception as err:
command_response("ping", False)
log.warning(
"[SCK] PING command execution error", e=err, command=received_json
)
2022-05-09 00:41:49 +00:00
2023-02-09 20:43:49 +00:00
def tnc_arq_connect(self, received_json):
2022-11-18 09:29:20 +00:00
2023-02-09 20:43:49 +00:00
# pause our beacon first
static.BEACON_PAUSE = True
2022-11-18 09:29:20 +00:00
2023-02-09 20:43:49 +00:00
# check for connection attempts key
try:
attempts = int(received_json["attempts"])
except Exception:
# 15 == self.session_connect_max_retries
attempts = 15
2022-11-18 09:29:20 +00:00
2023-02-09 20:43:49 +00:00
dxcallsign = received_json["dxcallsign"]
2023-02-09 20:43:49 +00:00
# check if specific callsign is set with different SSID than the TNC is initialized
try:
mycallsign = received_json["mycallsign"]
mycallsign = helpers.callsign_to_bytes(mycallsign)
mycallsign = helpers.bytes_to_callsign(mycallsign)
except Exception:
mycallsign = static.MYCALLSIGN
# additional step for being sure our callsign is correctly
# in case we are not getting a station ssid
# then we are forcing a station ssid = 0
dxcallsign = helpers.callsign_to_bytes(dxcallsign)
dxcallsign = helpers.bytes_to_callsign(dxcallsign)
if static.ARQ_SESSION_STATE not in ["disconnected", "failed"]:
command_response("connect", False)
log.warning(
"[SCK] Connect command execution error",
e=f"already connected to station:{static.DXCALLSIGN}",
command=received_json,
)
else:
# finally check again if we are disconnected or failed
2023-02-09 20:43:49 +00:00
# try connecting
try:
2023-02-09 20:43:49 +00:00
DATA_QUEUE_TRANSMIT.put(["CONNECT", mycallsign, dxcallsign, attempts])
command_response("connect", True)
except Exception as err:
2023-02-09 20:43:49 +00:00
command_response("connect", False)
log.warning(
2023-02-09 20:43:49 +00:00
"[SCK] Connect command execution error",
e=err,
command=received_json,
)
2023-02-09 20:43:49 +00:00
# allow beacon transmission again
static.BEACON_PAUSE = False
2022-05-09 00:41:49 +00:00
2023-02-09 20:43:49 +00:00
# allow beacon transmission again
static.BEACON_PAUSE = False
2023-02-09 20:43:49 +00:00
def tnc_arq_disconnect(self, received_json):
try:
if static.ARQ_SESSION_STATE not in ["disconnecting", "disconnected", "failed"]:
DATA_QUEUE_TRANSMIT.put(["DISCONNECT"])
2023-02-09 20:43:49 +00:00
# set early disconnecting state so we can interrupt connection attempts
static.ARQ_SESSION_STATE = "disconnecting"
command_response("disconnect", True)
else:
command_response("disconnect", False)
2023-01-22 14:19:42 +00:00
log.warning(
2023-02-09 20:43:49 +00:00
"[SCK] Disconnect command not possible",
state=static.ARQ_SESSION_STATE,
2023-01-22 14:19:42 +00:00
command=received_json,
)
2023-02-09 20:43:49 +00:00
except Exception as err:
command_response("disconnect", False)
log.warning(
"[SCK] Disconnect command execution error",
e=err,
command=received_json,
)
2023-01-22 14:19:42 +00:00
2023-02-09 20:43:49 +00:00
def tnc_arq_send_raw(self, received_json):
static.BEACON_PAUSE = True
2022-12-04 11:22:35 +00:00
2023-02-09 20:43:49 +00:00
# wait some random time
helpers.wait(randrange(5, 25, 5) / 10.0)
2022-05-09 00:41:49 +00:00
2023-02-09 20:43:49 +00:00
# we need to warn if already in arq state
if static.ARQ_STATE:
command_response("send_raw", False)
log.warning(
"[SCK] Send raw command execution warning",
e="already in arq state",
i="command queued",
command=received_json,
)
2022-11-20 11:02:53 +00:00
2023-02-09 20:43:49 +00:00
try:
if not static.ARQ_SESSION:
dxcallsign = received_json["parameter"][0]["dxcallsign"]
# additional step for being sure our callsign is correctly
# in case we are not getting a station ssid
# then we are forcing a station ssid = 0
dxcallsign = helpers.callsign_to_bytes(dxcallsign)
dxcallsign = helpers.bytes_to_callsign(dxcallsign)
static.DXCALLSIGN = dxcallsign
static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN)
command_response("send_raw", True)
else:
dxcallsign = static.DXCALLSIGN
static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN)
2023-02-09 20:43:49 +00:00
mode = int(received_json["parameter"][0]["mode"])
n_frames = int(received_json["parameter"][0]["n_frames"])
base64data = received_json["parameter"][0]["data"]
2022-05-09 00:41:49 +00:00
2023-02-09 20:43:49 +00:00
# check if specific callsign is set with different SSID than the TNC is initialized
try:
mycallsign = received_json["parameter"][0]["mycallsign"]
mycallsign = helpers.callsign_to_bytes(mycallsign)
mycallsign = helpers.bytes_to_callsign(mycallsign)
2023-02-09 20:43:49 +00:00
except Exception:
mycallsign = static.MYCALLSIGN
2023-02-09 20:43:49 +00:00
# check for connection attempts key
try:
attempts = int(received_json["parameter"][0]["attempts"])
2023-02-09 20:43:49 +00:00
except Exception:
attempts = 10
2023-02-09 20:43:49 +00:00
# check if transmission uuid provided else set no-uuid
try:
2023-02-09 20:43:49 +00:00
arq_uuid = received_json["uuid"]
except Exception:
arq_uuid = "no-uuid"
if len(base64data) % 4:
raise TypeError
binarydata = base64.b64decode(base64data)
DATA_QUEUE_TRANSMIT.put(
["ARQ_RAW", binarydata, mode, n_frames, arq_uuid, mycallsign, dxcallsign, attempts]
)
except Exception as err:
command_response("send_raw", False)
log.warning(
"[SCK] Send raw command execution error",
e=err,
command=received_json,
)
2022-05-09 00:41:49 +00:00
2023-02-09 20:43:49 +00:00
def tnc_arq_stop_transmission(self, received_json):
try:
if static.TNC_STATE == "BUSY" or static.ARQ_STATE:
DATA_QUEUE_TRANSMIT.put(["STOP"])
log.warning("[SCK] Stopping transmission!")
static.TNC_STATE = "IDLE"
static.ARQ_STATE = False
command_response("stop_transmission", True)
except Exception as err:
command_response("stop_transmission", False)
log.warning(
"[SCK] STOP command execution error", e=err, command=received_json
)
def tnc_get_rx_buffer(self, received_json):
2023-02-10 11:12:18 +00:00
try:
if not RX_BUFFER.empty():
output = {
"command": "rx_buffer",
"data-array": [],
}
for _buffer_length in range(RX_BUFFER.qsize()):
base64_data = RX_BUFFER.queue[_buffer_length][4]
output["data-array"].append(
{
"uuid": RX_BUFFER.queue[_buffer_length][0],
"timestamp": RX_BUFFER.queue[_buffer_length][1],
"dxcallsign": str(RX_BUFFER.queue[_buffer_length][2], "utf-8"),
"dxgrid": str(RX_BUFFER.queue[_buffer_length][3], "utf-8"),
"data": base64_data,
}
)
jsondata = json.dumps(output)
# self.request.sendall(bytes(jsondata, encoding))
SOCKET_QUEUE.put(jsondata)
command_response("rx_buffer", True)
2022-05-09 00:41:49 +00:00
2023-02-10 11:12:18 +00:00
except Exception as err:
command_response("rx_buffer", False)
log.warning(
"[SCK] Send RX buffer command execution error",
e=err,
command=received_json,
)
2023-02-09 20:43:49 +00:00
def tnc_set_del_rx_buffer(self, received_json):
try:
RX_BUFFER.queue.clear()
command_response("del_rx_buffer", True)
except Exception as err:
command_response("del_rx_buffer", False)
log.warning(
"[SCK] Delete RX buffer command execution error",
e=err,
command=received_json,
)
2023-02-09 20:43:49 +00:00
def tnc_set_mode(self, received_json):
try:
RIGCTLD_COMMAND_QUEUE.put(["set_mode", received_json["mode"]])
command_response("set_mode", True)
except Exception as err:
command_response("set_mode", False)
log.warning(
"[SCK] Set mode command execution error",
e=err,
command=received_json,
)
2023-01-04 19:12:03 +00:00
2023-02-09 20:43:49 +00:00
def tnc_set_frequency(self, received_json):
try:
RIGCTLD_COMMAND_QUEUE.put(["set_frequency", received_json["frequency"]])
command_response("set_frequency", True)
except Exception as err:
command_response("set_frequency", False)
log.warning(
"[SCK] Set frequency command execution error",
e=err,
command=received_json,
)
2023-02-09 20:43:49 +00:00
# ------------------------ DAEMON COMMANDS
def process_daemon_commands(self, data):
"""
process daemon commands
2023-02-09 20:43:49 +00:00
Args:
data:
2023-02-09 20:43:49 +00:00
Returns:
2023-02-09 20:43:49 +00:00
"""
log = structlog.get_logger("process_daemon_commands")
2023-02-09 20:43:49 +00:00
# convert data to json object
received_json = json.loads(data)
log.debug("[SCK] CMD", command=received_json)
if received_json["type"] == "set" and received_json["command"] == "mycallsign":
self.daemon_set_mycallsign(received_json)
2023-02-09 20:43:49 +00:00
if received_json["type"] == "set" and received_json["command"] == "mygrid":
self.daemon_set_mygrid(received_json)
if (
2023-02-10 11:12:18 +00:00
received_json["type"] == "set"
and received_json["command"] == "start_tnc"
and not static.TNCSTARTED
2023-02-09 20:43:49 +00:00
):
self.daemon_start_tnc(received_json)
if received_json["type"] == "get" and received_json["command"] == "test_hamlib":
self.daemon_test_hamlib(received_json)
if received_json["type"] == "set" and received_json["command"] == "stop_tnc":
self.daemon_stop_tnc(received_json)
def daemon_set_mycallsign(self, received_json):
try:
callsign = received_json["parameter"]
2022-03-06 16:23:04 +00:00
if bytes(callsign, "utf-8") == b"":
self.request.sendall(b"INVALID CALLSIGN")
log.warning(
"[SCK] SET MYCALL FAILED",
call=static.MYCALLSIGN,
crc=static.MYCALLSIGN_CRC.hex(),
)
else:
static.MYCALLSIGN = bytes(callsign, "utf-8")
static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN)
command_response("mycallsign", True)
log.info(
"[SCK] SET MYCALL",
call=static.MYCALLSIGN,
crc=static.MYCALLSIGN_CRC.hex(),
)
except Exception as err:
command_response("mycallsign", False)
log.warning("[SCK] command execution error", e=err, command=received_json)
2022-05-09 00:41:49 +00:00
2023-02-09 20:43:49 +00:00
def daemon_set_mygrid(self, received_json):
try:
mygrid = received_json["parameter"]
if bytes(mygrid, "utf-8") == b"":
self.request.sendall(b"INVALID GRID")
command_response("mygrid", False)
else:
static.MYGRID = bytes(mygrid, "utf-8")
log.info("[SCK] SET MYGRID", grid=static.MYGRID)
command_response("mygrid", True)
except Exception as err:
command_response("mygrid", False)
log.warning("[SCK] command execution error", e=err, command=received_json)
2022-05-09 00:41:49 +00:00
2023-02-09 20:43:49 +00:00
def daemon_start_tnc(self, received_json):
try:
2023-02-02 21:41:35 +00:00
startparam = received_json["parameter"][0]
2023-02-09 20:43:49 +00:00
mycall = str(helpers.return_key_from_object("AA0AA", startparam, "mycall"))
mygrid = str(helpers.return_key_from_object("JN12ab", startparam, "mygrid"))
rx_audio = str(helpers.return_key_from_object("0", startparam, "rx_audio"))
tx_audio = str(helpers.return_key_from_object("0", startparam, "tx_audio"))
radiocontrol = str(helpers.return_key_from_object("disabled", startparam, "radiocontrol"))
rigctld_ip = str(helpers.return_key_from_object("127.0.0.1", startparam, "rigctld_ip"))
rigctld_port = str(helpers.return_key_from_object("4532", startparam, "rigctld_port"))
enable_scatter = str(helpers.return_key_from_object("True", startparam, "enable_scatter"))
enable_fft = str(helpers.return_key_from_object("True", startparam, "enable_fft"))
enable_fsk = str(helpers.return_key_from_object("False", startparam, "enable_fsk"))
low_bandwidth_mode = str(helpers.return_key_from_object("False", startparam, "low_bandwidth_mode"))
tuning_range_fmin = str(helpers.return_key_from_object("-50", startparam, "tuning_range_fmin"))
tuning_range_fmax = str(helpers.return_key_from_object("50", startparam, "tuning_range_fmax"))
tx_audio_level = str(helpers.return_key_from_object("100", startparam, "tx_audio_level"))
respond_to_cq = str(helpers.return_key_from_object("False", startparam, "respond_to_cq"))
rx_buffer_size = str(helpers.return_key_from_object("16", startparam, "rx_buffer_size"))
enable_explorer = str(helpers.return_key_from_object("False", startparam, "enable_explorer"))
enable_auto_tune = str(helpers.return_key_from_object("False", startparam, "enable_auto_tune"))
enable_stats = str(helpers.return_key_from_object("False", startparam, "enable_stats"))
try:
# convert ssid list to python list
2023-02-02 22:49:52 +00:00
ssid_list = str(helpers.return_key_from_object("0, 1, 2, 3, 4, 5, 6, 7, 8, 9", startparam, "ssid_list"))
ssid_list = ssid_list.replace(" ", "")
ssid_list = ssid_list.split(",")
# convert str to int
ssid_list = list(map(int, ssid_list))
except KeyError:
2023-02-02 22:49:52 +00:00
ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2022-04-02 16:40:12 +00:00
# print some debugging parameters
2023-02-02 22:49:52 +00:00
for item in startparam:
log.debug(
f"[SCK] TNC Startup Config : {item}",
2023-02-02 22:49:52 +00:00
value=startparam[item],
)
DAEMON_QUEUE.put(
[
"STARTTNC",
mycall,
mygrid,
rx_audio,
tx_audio,
radiocontrol,
rigctld_ip,
rigctld_port,
enable_scatter,
enable_fft,
low_bandwidth_mode,
tuning_range_fmin,
tuning_range_fmax,
enable_fsk,
tx_audio_level,
respond_to_cq,
rx_buffer_size,
2022-11-05 21:27:33 +00:00
enable_explorer,
ssid_list,
enable_auto_tune,
2023-02-02 21:41:35 +00:00
enable_stats
]
)
command_response("start_tnc", True)
2022-05-09 00:41:49 +00:00
except Exception as err:
2022-05-09 00:41:49 +00:00
command_response("start_tnc", False)
log.warning("[SCK] command execution error", e=err, command=received_json)
2023-02-09 20:43:49 +00:00
def daemon_stop_tnc(self, received_json):
try:
static.TNCPROCESS.kill()
# unregister process from atexit to avoid process zombies
atexit.unregister(static.TNCPROCESS.kill)
log.warning("[SCK] Stopping TNC")
static.TNCSTARTED = False
command_response("stop_tnc", True)
except Exception as err:
command_response("stop_tnc", False)
log.warning("[SCK] command execution error", e=err, command=received_json)
def daemon_test_hamlib(self, received_json):
try:
radiocontrol = str(received_json["parameter"][0]["radiocontrol"])
rigctld_ip = str(received_json["parameter"][0]["rigctld_ip"])
rigctld_port = str(received_json["parameter"][0]["rigctld_port"])
DAEMON_QUEUE.put(
[
"TEST_HAMLIB",
radiocontrol,
rigctld_ip,
rigctld_port,
]
)
command_response("test_hamlib", True)
except Exception as err:
2022-05-09 00:41:49 +00:00
command_response("test_hamlib", False)
log.warning("[SCK] command execution error", e=err, command=received_json)
2022-05-09 00:41:49 +00:00
def send_daemon_state():
"""
send the daemon state to network
"""
2022-06-01 00:35:35 +00:00
log = structlog.get_logger("send_daemon_state")
2022-02-15 17:10:14 +00:00
try:
python_version = f"{str(sys.version_info[0])}.{str(sys.version_info[1])}"
2022-02-15 17:10:14 +00:00
output = {
"command": "daemon_state",
"daemon_state": [],
"python_version": str(python_version),
"input_devices": static.AUDIO_INPUT_DEVICES,
"output_devices": static.AUDIO_OUTPUT_DEVICES,
"serial_devices": static.SERIAL_DEVICES,
2023-02-10 11:12:18 +00:00
# 'cpu': str(psutil.cpu_percent()),
# 'ram': str(psutil.virtual_memory().percent),
"version": "0.1",
}
2022-05-09 00:41:49 +00:00
2022-02-15 17:10:14 +00:00
if static.TNCSTARTED:
output["daemon_state"].append({"status": "running"})
else:
output["daemon_state"].append({"status": "stopped"})
2022-05-09 00:41:49 +00:00
2022-05-23 12:02:22 +00:00
return json.dumps(output)
except Exception as err:
log.warning("[SCK] error", e=err)
return None
2022-05-09 00:41:49 +00:00
2023-02-10 11:12:18 +00:00
2023-02-09 20:43:49 +00:00
def send_tnc_state():
"""
send the tnc state to network
"""
encoding = "utf-8"
output = {
"command": "tnc_state",
"ptt_state": str(static.PTT_STATE),
"tnc_state": str(static.TNC_STATE),
"arq_state": str(static.ARQ_STATE),
"arq_session": str(static.ARQ_SESSION),
"arq_session_state": str(static.ARQ_SESSION_STATE),
"audio_dbfs": str(static.AUDIO_DBFS),
"snr": str(static.SNR),
"frequency": str(static.HAMLIB_FREQUENCY),
"rf_level": str(static.HAMLIB_RF),
"strength": str(static.HAMLIB_STRENGTH),
"alc": str(static.HAMLIB_ALC),
"audio_level": str(static.TX_AUDIO_LEVEL),
"audio_auto_tune": str(static.AUDIO_AUTO_TUNE),
"speed_level": str(static.ARQ_SPEED_LEVEL),
"mode": str(static.HAMLIB_MODE),
"bandwidth": str(static.HAMLIB_BANDWIDTH),
"fft": str(static.FFT),
"channel_busy": str(static.CHANNEL_BUSY),
2023-02-14 21:56:23 +00:00
"is_codec2_traffic": str(static.IS_CODEC2_TRAFFIC),
2023-02-09 20:43:49 +00:00
"scatter": static.SCATTER,
"rx_buffer_length": str(RX_BUFFER.qsize()),
"rx_msg_buffer_length": str(len(static.RX_MSG_BUFFER)),
"arq_bytes_per_minute": str(static.ARQ_BYTES_PER_MINUTE),
"arq_bytes_per_minute_burst": str(static.ARQ_BYTES_PER_MINUTE_BURST),
"arq_seconds_until_finish": str(static.ARQ_SECONDS_UNTIL_FINISH),
"arq_compression_factor": str(static.ARQ_COMPRESSION_FACTOR),
"arq_transmission_percent": str(static.ARQ_TRANSMISSION_PERCENT),
"speed_list": static.SPEED_LIST,
"total_bytes": str(static.TOTAL_BYTES),
"beacon_state": str(static.BEACON_STATE),
"stations": [],
"mycallsign": str(static.MYCALLSIGN, encoding),
"mygrid": str(static.MYGRID, encoding),
"dxcallsign": str(static.DXCALLSIGN, encoding),
"dxgrid": str(static.DXGRID, encoding),
"hamlib_status": static.HAMLIB_STATUS,
"listen": str(static.LISTEN),
"audio_recording": str(static.AUDIO_RECORD),
}
# 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],
}
)
return json.dumps(output)
def command_response(command, status):
s_status = "OK" if status else "Failed"
jsondata = {"command_response": command, "status": s_status}
2022-06-24 19:22:16 +00:00
data_out = json.dumps(jsondata)
SOCKET_QUEUE.put(data_out)
2022-12-27 21:57:54 +00:00
2023-02-02 21:41:35 +00:00
def try_except(string):
try:
return string
except Exception:
2023-02-10 11:12:18 +00:00
return False