FreeDATA/tnc/daemon.py
2022-05-19 16:48:21 +02:00

375 lines
14 KiB
Python
Executable file

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
daemon.py
Author: DJ2LS, January 2022
daemon for providing basic information for the tnc like audio or serial devices
"""
# pylint: disable=invalid-name, line-too-long, c-extension-no-member
# pylint: disable=import-outside-toplevel
import argparse
import atexit
import multiprocessing
import os
import queue
import re
import signal
import socketserver
import subprocess
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 log_handler
import sock
import static
# signal handler for closing aplication
def signal_handler(sig, frame):
"""
Signal handler for closing the network socket on app exit
Args:
sig:
frame:
Returns: system exit
"""
print('Closing daemon...')
sock.CLOSE_SIGNAL = True
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
class DAEMON():
"""
Daemon class
"""
def __init__(self):
# load crc engine
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.start()
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)
worker.start()
def update_audio_devices(self):
"""
Update audio devices and set to static
"""
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)
# print(e)
time.sleep(1)
def update_serial_devices(self):
"""
Update serial devices and set to static
"""
while 1:
try:
#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 = crc_hwid.hex()
description = f"{desc} [{crc_hwid}]"
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)
# print(e)
def worker(self):
"""
Worker to handle the received commands
"""
while 1:
try:
data = self.daemon_queue.get()
# data[1] mycall
# data[2] mygrid
# data[3] rx_audio
# data[4] tx_audio
# data[5] devicename
# data[6] deviceport
# data[7] serialspeed
# data[8] pttprotocol
# data[9] pttport
# data[10] data_bits
# data[11] stop_bits
# data[12] handshake
# data[13] radiocontrol
# data[14] rigctld_ip
# data[15] rigctld_port
# data[16] send_scatter
# data[17] send_fft
# data[18] low_bandwith_mode
# data[19] tuning_range_fmin
# data[20] tuning_range_fmax
# data[21] enable FSK
# 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])
# list of parameters, necessary for running subprocess command as a list
options = []
options.append('--port')
options.append(str(static.DAEMONPORT - 1))
options.append('--mycall')
options.append(data[1])
options.append('--mygrid')
options.append(data[2])
options.append('--rx')
options.append(data[3])
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')
options.append(data[5])
options.append('--deviceport')
options.append(data[6])
options.append('--serialspeed')
options.append(data[7])
options.append('--pttprotocol')
options.append(data[8])
options.append('--pttport')
options.append(data[9])
options.append('--data_bits')
options.append(data[10])
options.append('--stop_bits')
options.append(data[11])
options.append('--handshake')
options.append(data[12])
options.append('--radiocontrol')
options.append(data[13])
if data[13] == 'rigctld':
options.append('--rigctld_ip')
options.append(data[14])
options.append('--rigctld_port')
options.append(data[15])
if data[16] == 'True':
options.append('--scatter')
if data[17] == 'True':
options.append('--fft')
if data[18] == 'True':
options.append('--500hz')
options.append('--tuning_range_fmin')
options.append(data[19])
options.append('--tuning_range_fmax')
options.append(data[20])
# overriding FSK mode
#if data[21] == 'True':
# options.append('--fsk')
options.append('--tx-audio-level')
options.append(data[22])
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')
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)
command = []
if sys.platform in ['linux', 'darwin']:
command.append('python3')
elif sys.platform in ['win32', 'win64']:
command.append('python')
command.append('main.py')
command += options
p = subprocess.Popen(command)
atexit.register(p.kill)
structlog.get_logger("structlog").info("[DMN] TNC started", path="source")
static.TNCPROCESS = p # .pid
static.TNCSTARTED = True
'''
# WE HAVE THIS PART in SOCKET
if data[0] == 'STOPTNC':
static.TNCPROCESS.kill()
structlog.get_logger("structlog").warning("[DMN] Stopping TNC")
#os.kill(static.TNCPROCESS, signal.SIGKILL)
static.TNCSTARTED = False
'''
# data[1] devicename
# data[2] deviceport
# data[3] serialspeed
# data[4] pttprotocol
# data[5] pttport
# data[6] data_bits
# data[7] stop_bits
# data[8] handshake
# data[9] radiocontrol
# data[10] rigctld_ip
# data[11] rigctld_port
if data[0] == 'TEST_HAMLIB':
devicename = data[1]
deviceport = data[2]
serialspeed = data[3]
pttprotocol = data[4]
pttport = data[5]
data_bits = data[6]
stop_bits = data[7]
handshake = data[8]
radiocontrol = data[9]
rigctld_ip = data[10]
rigctld_port = data[11]
# check how we want to control the radio
if radiocontrol == 'direct':
import rig
elif radiocontrol == 'rigctl':
import rigctl as rig
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_version = rig.hamlib_version
hamlib.set_ptt(True)
pttstate = hamlib.get_ptt()
if pttstate:
structlog.get_logger("structlog").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'}
else:
structlog.get_logger("structlog").error("[DMN] Hamlib PTT", status='FAILED')
response = {'command': 'test_hamlib', 'result': 'FAILED'}
hamlib.set_ptt(False)
hamlib.close_rig()
jsondata = json.dumps(response)
sock.SOCKET_QUEUE.put(jsondata)
except Exception as e:
structlog.get_logger("structlog").error("[DMN] worker: Exception: ", e=e)
# print(e)
if __name__ == '__main__':
# 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)
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 == '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 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)
try:
structlog.get_logger("structlog").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)
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)
sys.exit(1)
daemon = DAEMON()
structlog.get_logger("structlog").info("[DMN] Starting FreeDATA Daemon", author="DJ2LS", year="2022", version=static.VERSION)
while True:
time.sleep(1)