Merge pull request #187 from DJ2LS/refactor_N2KIQ_202205

Refactor TNC modules
This commit is contained in:
DJ2LS 2022-05-15 20:45:33 +02:00 committed by GitHub
commit 2a109844e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1741 additions and 1995 deletions

View file

@ -1,16 +1,14 @@
import json
import sys
import multiprocessing
import sounddevice as sd
import atexit
import json
import multiprocessing
import sys
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
@ -26,7 +24,6 @@ def get_audio_devices():
sd._initialize()
with multiprocessing.Manager() as manager:
proxy_input_devices = manager.list()
proxy_output_devices = manager.list()
#print(multiprocessing.get_start_method())
@ -47,10 +44,8 @@ def fetch_audio_devices(input_devices, output_devices):
Returns:
"""
devices = sd.query_devices(device=None, kind=None)
index = 0
for device in devices:
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
try:
@ -66,8 +61,6 @@ def fetch_audio_devices(input_devices, output_devices):
name = ''
if maxInputChannels > 0:
input_devices.append({"id": index, "name": str(name)})
input_devices.append({"id": index, "name": name})
if maxOutputChannels > 0:
output_devices.append({"id": index, "name": str(name)})
index += 1
output_devices.append({"id": index, "name": name})

View file

@ -1,21 +1,24 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# pylint: disable=invalid-name, line-too-long, c-extension-no-member
# pylint: disable=import-outside-toplevel
import ctypes
from ctypes import *
import sys
import os
from enum import Enum
import numpy as np
from threading import Lock
import glob
import os
import sys
from enum import Enum
from threading import Lock
import numpy as np
import structlog
# Enum for codec2 modes
class FREEDV_MODE(Enum):
"""
enum for codec2 modes and names
Enumeration for codec2 modes and names
"""
fsk_ldpc_0 = 200
fsk_ldpc_1 = 201
@ -25,51 +28,50 @@ class FREEDV_MODE(Enum):
datac3 = 12
allmodes = 255
# function for returning the mode value
def freedv_get_mode_value_by_name(mode):
# Function for returning the mode value
def freedv_get_mode_value_by_name(mode: str) -> int:
"""
get the codec2 mode by entering its string
Get the codec2 mode by entering its string
Args:
mode:
Returns: int
Returns:
int
"""
return FREEDV_MODE[mode].value
# function for returning the mode name
def freedv_get_mode_name_by_value(mode):
# Function for returning the mode name
def freedv_get_mode_name_by_value(mode: int) -> str:
"""
get the codec2 mode name as string
Args:
mode:
Returns: string
Returns:
string
"""
return FREEDV_MODE(mode).name
# check if we are running in a pyinstaller environment
try:
app_path = sys._MEIPASS
except:
app_path = os.path.abspath(".")
sys.path.append(app_path)
# Check if we are running in a pyinstaller environment
if hasattr(sys, "_MEIPASS"):
sys.path.append(getattr(sys, "_MEIPASS"))
else:
sys.path.append(os.path.abspath("."))
structlog.get_logger("structlog").info("[C2 ] Searching for libcodec2...")
if sys.platform == 'linux':
files = glob.glob('**/*libcodec2*',recursive=True)
files = glob.glob(r'**/*libcodec2*',recursive=True)
files.append('libcodec2.so')
elif sys.platform == 'darwin':
files = glob.glob('**/*libcodec2*.dylib',recursive=True)
elif sys.platform == 'win32' or sys.platform == 'win64':
files = glob.glob('**\*libcodec2*.dll',recursive=True)
files = glob.glob(r'**/*libcodec2*.dylib',recursive=True)
elif sys.platform in ['win32', 'win64']:
files = glob.glob(r'**\*libcodec2*.dll',recursive=True)
else:
files = []
api = None
for file in files:
try:
api = ctypes.CDLL(file)
@ -78,67 +80,63 @@ for file in files:
except Exception 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 not 'api' in locals():
structlog.get_logger("structlog").critical("[C2 ] Libcodec2 not loaded", path=file)
os._exit(1)
# 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")
sys.exit(1)
# ctypes function init
#api.freedv_set_tuning_range.restype = c_int
#api.freedv_set_tuning_range.argype = [c_void_p, c_float, 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 = [c_int]
api.freedv_open.restype = c_void_p
api.freedv_open.argype = [ctypes.c_int]
api.freedv_open.restype = ctypes.c_void_p
api.freedv_open_advanced.argtype = [c_int, c_void_p]
api.freedv_open_advanced.restype = c_void_p
api.freedv_open_advanced.argtype = [ctypes.c_int, ctypes.c_void_p]
api.freedv_open_advanced.restype = ctypes.c_void_p
api.freedv_get_bits_per_modem_frame.argtype = [c_void_p]
api.freedv_get_bits_per_modem_frame.restype = c_int
api.freedv_get_bits_per_modem_frame.argtype = [ctypes.c_void_p]
api.freedv_get_bits_per_modem_frame.restype = ctypes.c_int
api.freedv_nin.argtype = [c_void_p]
api.freedv_nin.restype = c_int
api.freedv_nin.argtype = [ctypes.c_void_p]
api.freedv_nin.restype = ctypes.c_int
api.freedv_rawdatarx.argtype = [c_void_p, c_char_p, c_char_p]
api.freedv_rawdatarx.restype = c_int
api.freedv_rawdatarx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p]
api.freedv_rawdatarx.restype = ctypes.c_int
api.freedv_rawdatatx.argtype = [c_void_p, c_char_p, c_char_p]
api.freedv_rawdatatx.restype = c_int
api.freedv_rawdatatx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p]
api.freedv_rawdatatx.restype = ctypes.c_int
api.freedv_rawdatapostambletx.argtype = [c_void_p, c_char_p, c_char_p]
api.freedv_rawdatapostambletx.restype = c_int
api.freedv_rawdatapostambletx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p]
api.freedv_rawdatapostambletx.restype = ctypes.c_int
api.freedv_rawdatapreambletx.argtype = [c_void_p, c_char_p, c_char_p]
api.freedv_rawdatapreambletx.restype = c_int
api.freedv_rawdatapreambletx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p]
api.freedv_rawdatapreambletx.restype = ctypes.c_int
api.freedv_get_n_max_modem_samples.argtype = [c_void_p]
api.freedv_get_n_max_modem_samples.restype = c_int
api.freedv_get_n_max_modem_samples.argtype = [ctypes.c_void_p]
api.freedv_get_n_max_modem_samples.restype = ctypes.c_int
api.freedv_set_frames_per_burst.argtype = [c_void_p, c_int]
api.freedv_set_frames_per_burst.restype = c_void_p
api.freedv_set_frames_per_burst.argtype = [ctypes.c_void_p, ctypes.c_int]
api.freedv_set_frames_per_burst.restype = ctypes.c_void_p
api.freedv_get_rx_status.argtype = [c_void_p]
api.freedv_get_rx_status.restype = c_int
api.freedv_get_rx_status.argtype = [ctypes.c_void_p]
api.freedv_get_rx_status.restype = ctypes.c_int
api.freedv_get_modem_stats.argtype = [c_void_p, c_void_p, c_void_p]
api.freedv_get_modem_stats.restype = 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.restype = ctypes.c_int
api.freedv_get_n_tx_postamble_modem_samples.argtype = [c_void_p]
api.freedv_get_n_tx_postamble_modem_samples.restype = c_int
api.freedv_get_n_tx_postamble_modem_samples.argtype = [ctypes.c_void_p]
api.freedv_get_n_tx_postamble_modem_samples.restype = ctypes.c_int
api.freedv_get_n_tx_preamble_modem_samples.argtype = [c_void_p]
api.freedv_get_n_tx_preamble_modem_samples.restype = c_int
api.freedv_get_n_tx_preamble_modem_samples.argtype = [ctypes.c_void_p]
api.freedv_get_n_tx_preamble_modem_samples.restype = ctypes.c_int
api.freedv_get_n_tx_modem_samples.argtype = [c_void_p]
api.freedv_get_n_tx_modem_samples.restype = c_int
api.freedv_get_n_tx_modem_samples.argtype = [ctypes.c_void_p]
api.freedv_get_n_tx_modem_samples.restype = ctypes.c_int
api.freedv_get_n_max_modem_samples.argtype = [c_void_p]
api.freedv_get_n_max_modem_samples.restype = c_int
api.freedv_get_n_max_modem_samples.argtype = [ctypes.c_void_p]
api.freedv_get_n_max_modem_samples.restype = ctypes.c_int
api.FREEDV_FS_8000 = 8000
api.FREEDV_MODE_DATAC1 = 10
@ -148,7 +146,7 @@ api.FREEDV_MODE_FSK_LDPC = 9
# -------------------------------- FSK LDPC MODE SETTINGS
# advanced structure for fsk modes
# Advanced structure for fsk modes
class ADVANCED(ctypes.Structure):
""" """
_fields_ = [
@ -203,7 +201,6 @@ 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
# ------- MODEM STATS STRUCTURES
MODEM_STATS_NC_MAX = 50 + 1
MODEM_STATS_NR_MAX = 160
MODEM_STATS_ET_MAX = 8
@ -211,7 +208,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
# Modem stats structure
class MODEMSTATS(ctypes.Structure):
""" """
_fields_ = [
@ -233,8 +231,6 @@ class MODEMSTATS(ctypes.Structure):
("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
@ -259,22 +255,22 @@ api.rx_sync_flags_to_text = [
"EBS-",
"EBST"]
# audio buffer ---------------------------------------------------------
# Audio buffer ---------------------------------------------------------
class audio_buffer:
"""
thread safe audio buffer, which fits to needs of codec2
Thread safe audio buffer, which fits to needs of codec2
made by David Rowe, VK5DGR
"""
# a buffer of int16 samples, using a fixed length numpy array self.buffer for storage
# 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):
"""
Push new data to buffer
@ -283,14 +279,15 @@ class audio_buffer:
samples:
Returns:
Nothing
"""
self.mutex.acquire()
# add samples at the end of the buffer
# Add samples at the end of the buffer
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):
"""
get data from buffer in size of NIN
@ -298,90 +295,89 @@ class audio_buffer:
size:
Returns:
Nothing
"""
self.mutex.acquire()
# remove samples from the start of the buffer
self.nbuffer -= size;
# Remove samples from the start of the buffer
self.nbuffer -= size
self.buffer[:self.nbuffer] = self.buffer[size:size+self.nbuffer]
assert self.nbuffer >= 0
self.mutex.release()
# resampler ---------------------------------------------------------
# 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 = [c_void_p, c_void_p, c_int]
api.fdmdv_48_to_8_short.argtype = [c_void_p, c_void_p, c_int]
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]
class resampler:
"""
resampler class
Re-sampler class
"""
# resample an array of variable length, we just store the filter memories here
# 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
def __init__(self):
structlog.get_logger("structlog").debug("[C2 ] create 48<->8 kHz resampler")
structlog.get_logger("structlog").debug("[C2 ] Create 48<->8 kHz resampler")
self.filter_mem8 = np.zeros(self.MEM8, dtype=np.int16)
self.filter_mem48 = np.zeros(self.MEM48)
def resample48_to_8(self, in48):
"""
audio resampler integration from codec2
downsample audio from 48000Hz to 8000Hz
Audio resampler integration from codec2
Downsample audio from 48000Hz to 8000Hz
Args:
in48: input data as np.int16
Returns: downsampled 8000Hz data as np.int16
Returns:
Downsampled 8000Hz data as np.int16
"""
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)
# Length of input vector must be an integer multiple of api.FDMDV_OS_48
assert len(in48) % api.FDMDV_OS_48 == 0
# concat filter memory and input samples
# 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
# In C: pin48=&in48_mem[MEM48]
pin48 = byref(np.ctypeslib.as_ctypes(in48_mem), 2*self.MEM48)
pin48 = ctypes.byref(np.ctypeslib.as_ctypes(in48_mem), 2 * self.MEM48)
n8 = int(len(in48) / api.FDMDV_OS_48)
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)
# store memory for next time
# Store memory for next time
self.filter_mem48 = in48_mem[:self.MEM48]
return out8
def resample8_to_48(self, in8):
"""
audio resampler integration from codec2
resample audio from 8000Hz to 48000Hz
Audio resampler integration from codec2
Re-sample audio from 8000Hz to 48000Hz
Args:
in8: input data as np.int16
Returns: 48000Hz audio as np.int16
Returns:
48000Hz audio as np.int16
"""
assert in8.dtype == np.int16
# concat filter memory and input samples
# 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
# In C: pin8=&in8_mem[MEM8]
pin8 = byref(np.ctypeslib.as_ctypes(in8_mem), 2*self.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));
# store memory for next time
# Store memory for next time
self.filter_mem8 = in8_mem[:self.MEM8]
return out48

View file

@ -8,55 +8,57 @@ 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 threading
import socketserver
import time
import sys
import subprocess
import ujson as json
import psutil
import serial.tools.list_ports
import static
import crcengine
import re
import structlog
import log_handler
import helpers
import atexit
import multiprocessing
import os
import queue
import audio
import sock
import atexit
import re
import signal
import multiprocessing
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
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
Daemon class
"""
def __init__(self):
# load crc engine
self.crc_algorithm = crcengine.new('crc16-ccitt-false') # load crc8 library
@ -70,25 +72,22 @@ class DAEMON():
worker = threading.Thread(target=self.worker, name="WORKER", daemon=True)
worker.start()
def update_audio_devices(self):
"""
update audio devices and set to static
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:
print(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
Update serial devices and set to static
"""
while 1:
try:
@ -96,26 +95,25 @@ class DAEMON():
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 = desc + ' [' + crc_hwid + ']'
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:
print(e)
structlog.get_logger("structlog").error("[DMN] update_serial_devices: Exception gathering serial devices:", e=e)
# print(e)
def worker(self):
"""
a worker for the received commands
Worker to handle the received commands
"""
while 1:
try:
data = self.daemon_queue.get()
# data[1] mycall
@ -168,7 +166,6 @@ class DAEMON():
# disabled mode
if data[13] != 'disabled':
options.append('--devicename')
options.append(data[5])
@ -212,7 +209,6 @@ class DAEMON():
if data[18] == 'True':
options.append('--500hz')
options.append('--tuning_range_fmin')
options.append(data[19])
@ -229,15 +225,13 @@ class DAEMON():
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 running tnc from binary, else run from source
# This helps running the tnc in a developer environment
try:
command = []
if sys.platform == 'linux' or sys.platform == 'darwin':
if sys.platform in ['linux', 'darwin']:
command.append('./freedata-tnc')
elif sys.platform == 'win32' or sys.platform == 'win64':
elif sys.platform in ['win32', 'win64']:
command.append('freedata-tnc.exe')
command += options
@ -246,11 +240,12 @@ class DAEMON():
atexit.register(p.kill)
structlog.get_logger("structlog").info("[DMN] TNC started", path="binary")
except:
except FileNotFoundError as e:
structlog.get_logger("structlog").error("[DMN] worker: Exception:", e=e)
command = []
if sys.platform == 'linux' or sys.platform == 'darwin':
if sys.platform in ['linux', 'darwin']:
command.append('python3')
elif sys.platform == 'win32' or sys.platform == 'win64':
elif sys.platform in ['win32', 'win64']:
command.append('python')
command.append('main.py')
@ -282,7 +277,6 @@ class DAEMON():
# data[10] rigctld_ip
# data[11] rigctld_port
if data[0] == 'TEST_HAMLIB':
devicename = data[1]
deviceport = data[2]
serialspeed = data[3]
@ -295,8 +289,6 @@ class DAEMON():
rigctld_ip = data[10]
rigctld_port = data[11]
# check how we want to control the radio
if radiocontrol == 'direct':
import rig
@ -308,7 +300,9 @@ class DAEMON():
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
@ -332,15 +326,13 @@ class DAEMON():
sock.SOCKET_QUEUE.put(jsondata)
except Exception as e:
print(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)
@ -348,7 +340,6 @@ if __name__ == '__main__':
static.DAEMONPORT = ARGS.socket_port
try:
if sys.platform == 'linux':
logging_path = os.getenv("HOME") + '/.config/' + 'FreeDATA/' + 'daemon'
@ -356,14 +347,14 @@ if __name__ == '__main__':
if sys.platform == 'darwin':
logging_path = os.getenv("HOME") + '/Library/' + 'Application Support/' + 'FreeDATA/' + 'daemon'
if sys.platform == 'win32' or sys.platform == 'win64':
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:
structlog.get_logger("structlog").error("[DMN] logger init error")
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)
@ -376,10 +367,9 @@ if __name__ == '__main__':
except Exception as e:
structlog.get_logger("structlog").error("[DMN] Starting TCP/IP socket failed", port=static.DAEMONPORT, e=e)
os._exit(1)
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)

File diff suppressed because it is too large Load diff

View file

@ -5,21 +5,21 @@ Created on Fri Dec 25 21:25:14 2020
@author: DJ2LS
"""
import time
import crcengine
import structlog
import static
def wait(seconds):
def wait(seconds: float) -> bool:
"""
Args:
seconds:
Returns:
"""
timeout = time.time() + seconds
@ -27,9 +27,7 @@ def wait(seconds):
time.sleep(0.01)
return True
def get_crc_8(data):
def get_crc_8(data) -> bytes:
"""Author: DJ2LS
Get the CRC8 of a byte string
@ -40,15 +38,14 @@ def get_crc_8(data):
data:
Returns:
CRC-8 (CCITT) of the provided data as bytes
"""
crc_algorithm = crcengine.new('crc8-ccitt') # load crc8 library
crc_data = crc_algorithm(data)
crc_data = crc_data.to_bytes(1, byteorder='big')
return crc_data
def get_crc_16(data):
def get_crc_16(data) -> bytes:
"""Author: DJ2LS
Get the CRC16 of a byte string
@ -59,14 +56,14 @@ def get_crc_16(data):
data:
Returns:
CRC-16 (CCITT) of the provided data as bytes
"""
crc_algorithm = crcengine.new('crc16-ccitt-false') # load crc16 library
crc_data = crc_algorithm(data)
crc_data = crc_data.to_bytes(2, byteorder='big')
return crc_data
def get_crc_24(data):
def get_crc_24(data) -> bytes:
"""Author: DJ2LS
Get the CRC24-OPENPGP of a byte string
@ -78,7 +75,7 @@ def get_crc_24(data):
data:
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,
@ -87,8 +84,7 @@ def get_crc_24(data):
crc_data = crc_data.to_bytes(3, byteorder='big')
return crc_data
def get_crc_32(data):
def get_crc_32(data: bytes) -> bytes:
"""Author: DJ2LS
Get the CRC32 of a byte string
@ -99,14 +95,13 @@ def get_crc_32(data):
data:
Returns:
CRC-32 of the provided data as bytes
"""
crc_algorithm = crcengine.new('crc32') # load crc16 library
crc_algorithm = crcengine.new('crc32') # load crc32 library
crc_data = crc_algorithm(data)
crc_data = crc_data.to_bytes(4, byteorder='big')
return crc_data
def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency):
"""
@ -119,33 +114,29 @@ def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency):
frequency:
Returns:
Nothing
"""
# check if buffer empty
if len(static.HEARD_STATIONS) == 0:
static.HEARD_STATIONS.append([dxcallsign, dxgrid, int(time.time()), datatype, snr, offset, frequency])
# if not, we search and update
else:
for i in range(0, len(static.HEARD_STATIONS)):
# update callsign with new timestamp
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]
break
# insert if nothing found
# Insert if nothing found
if i == len(static.HEARD_STATIONS) - 1:
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):
def callsign_to_bytes(callsign) -> bytes:
"""
Args:
@ -172,18 +163,20 @@ def callsign_to_bytes(callsign):
#-14 Truckers or generally full time drivers
#-15 generic additional station, digi, mobile, wx, etc
# try converting to bytestring if possible type string
# Try converting to bytestring if possible type string
try:
callsign = bytes(callsign, 'utf-8')
except:
except TypeError as e:
structlog.get_logger("structlog").debug("[HLP] callsign_to_bytes: Exception converting callsign to bytes:", e=e)
pass
# we need to do this step to reduce the needed paypload by the callsign ( stripping "-" out of the callsign )
# 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:
ssid = 0
except IndexError as e:
structlog.get_logger("structlog").debug("[HLP] callsign_to_bytes: Error callsign SSID to integer:", e=e)
#callsign = callsign[0]
#bytestring = bytearray(8)
@ -194,11 +187,9 @@ def callsign_to_bytes(callsign):
callsign = callsign[0].decode("utf-8")
ssid = bytes([ssid]).decode("utf-8")
return encode_call(callsign + ssid)
#return bytes(bytestring)
def bytes_to_callsign(bytestring):
def bytes_to_callsign(bytestring: bytes) -> bytes:
"""
Convert our callsign, received by a frame to a callsign in a human readable format
@ -207,9 +198,7 @@ def bytes_to_callsign(bytestring):
Returns:
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
@ -244,9 +233,7 @@ def bytes_to_callsign(bytestring):
decoded = decode_call(bytestring)
callsign = decoded[:-1]
ssid = ord(bytes(decoded[-1], "utf-8"))
return bytes(callsign + "-" + str(ssid), "utf-8")
return bytes(f"{callsign}-{ssid}", "utf-8")
def check_callsign(callsign:bytes, crc_to_check:bytes):
"""
@ -261,13 +248,14 @@ def check_callsign(callsign:bytes, crc_to_check:bytes):
False
"""
print(callsign)
# print(callsign)
structlog.get_logger("structlog").debug("[HLP] check_callsign: Checking:", callsign=callsign)
try:
callsign = callsign.split(b'-')
callsign = callsign[0] # we want the callsign without SSID
# We want the callsign without SSID
callsign = callsign.split(b'-')[0]
except:
callsign = callsign
except Exception as e:
structlog.get_logger("structlog").debug("[HLP] check_callsign: Error callsign SSIG to integer:", e=e)
for ssid in static.SSID_LIST:
call_with_ssid = bytearray(callsign)
@ -282,8 +270,6 @@ def check_callsign(callsign:bytes, crc_to_check:bytes):
return [False, ""]
def encode_grid(grid):
"""
@auther: DB1UJ
@ -292,7 +278,7 @@ def encode_grid(grid):
Returns:
4 bytes contains 26 bit valid data with encoded grid locator
"""
out_code_word = int(0)
out_code_word = 0
grid = grid.upper() # upper case to be save
@ -302,18 +288,18 @@ def encode_grid(grid):
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 = out_code_word << 9 # shift 9 bit left having space next bits, letter A-R * A-R
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 = out_code_word | (int_val & 0b1111111) # using bit OR to add new value
out_code_word = out_code_word << 7 # shift 7 bit left having space next bits, letter A-X
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 = out_code_word | (int_val & 0b11111) # using bit OR to add new value
out_code_word = out_code_word << 5 # shift 5 bit left having space next bits, letter A-X
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 = out_code_word | (int_val & 0b11111) # using bit OR to add new value
out_code_word |= (int_val & 0b11111) # using bit OR to add new value
return out_code_word.to_bytes(length=4, byteorder='big')
@ -328,25 +314,24 @@ def decode_grid(b_code_word:bytes):
code_word = int.from_bytes(b_code_word, byteorder='big', signed=False)
grid = chr((code_word & 0b11111) + 65)
code_word = code_word >> 5
code_word >>= 5
grid = chr((code_word & 0b11111) + 65) + grid
code_word = code_word >> 7
code_word >>= 7
grid = str(int(code_word & 0b1111111)) + grid
if (code_word & 0b1111111) < 10:
grid = '0' + grid
code_word = code_word >> 9
grid = f'0{grid}'
code_word >>= 9
int_val = int(code_word & 0b111111111)
int_first = int_val // 18
int_sec = int_val % 18
int_first, int_sec = divmod(int_val, 18)
# int_first = int_val // 18
# int_sec = int_val % 18
grid = chr(int(int_first) + 65) + chr(int(int_sec) + 65) + grid
return grid
def encode_call(call):
"""
@auther: DB1UJ
@ -356,17 +341,17 @@ def encode_call(call):
Returns:
6 bytes contains 6 bits/sign encoded 8 char call sign with binary SSID (only upper letters + numbers, SSID)
"""
out_code_word = int(0)
out_code_word = 0
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 = out_code_word << 6 # shift left 6 bit, making space for a new char
out_code_word = out_code_word | (int_val & 0b111111) # bit OR adds the new char, masked with AND 0b111111
out_code_word = out_code_word >> 6 # clean last char
out_code_word = out_code_word << 6 # make clean space
out_code_word = out_code_word | (ord(call[-1]) & 0b111111) # add the SSID uncoded only 0 - 63
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')
@ -385,9 +370,8 @@ def decode_call(b_code_word:bytes):
call = str()
while code_word != 0:
call = chr((code_word & 0b111111)+48) + call
code_word = code_word >> 6
code_word >>= 6
call = call[0:-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

View file

@ -8,7 +8,6 @@ def setup_logging(filename):
Returns:
"""
import logging.config
import structlog

View file

@ -6,26 +6,23 @@ Created on Tue Dec 22 16:58:45 2020
@author: DJ2LS
main module for running the tnc
"""
import argparse
import threading
import static
import socketserver
import helpers
import data_handler
import structlog
import log_handler
import modem
import sys
import multiprocessing
import os
import signal
import socketserver
import sys
import threading
import time
import multiprocessing
import structlog
import data_handler
import helpers
import log_handler
import modem
import static
# signal handler for closing aplication
def signal_handler(sig, frame):
@ -75,7 +72,6 @@ if __name__ == '__main__':
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
@ -101,8 +97,8 @@ if __name__ == '__main__':
static.HAMLIB_STOP_BITS = str(ARGS.hamlib_stop_bits)
static.HAMLIB_HANDSHAKE = ARGS.hamlib_handshake
static.HAMLIB_RADIOCONTROL = ARGS.hamlib_radiocontrol
static.HAMLIB_RGICTLD_IP = ARGS.rigctld_ip
static.HAMLIB_RGICTLD_PORT = str(ARGS.rigctld_port)
static.HAMLIB_RIGCTLD_IP = ARGS.rigctld_ip
static.HAMLIB_RIGCTLD_PORT = str(ARGS.rigctld_port)
static.ENABLE_SCATTER = ARGS.send_scatter
static.ENABLE_FFT = ARGS.send_fft
static.ENABLE_FSK = ARGS.enable_fsk
@ -123,16 +119,14 @@ if __name__ == '__main__':
if sys.platform == 'darwin':
logging_path = os.getenv("HOME") + '/Library/' + 'Application Support/' + 'FreeDATA/' + 'tnc'
if sys.platform == 'win32' or sys.platform == 'win64':
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:
structlog.get_logger("structlog").error("[DMN] logger init error")
except Exception as e:
structlog.get_logger("structlog").error("[DMN] logger init error", exception=e)
structlog.get_logger("structlog").info("[TNC] Starting FreeDATA", author="DJ2LS", year="2022", version=static.VERSION)
@ -142,9 +136,7 @@ if __name__ == '__main__':
# start modem
modem = modem.RF()
# --------------------------------------------START CMD SERVER
try:
structlog.get_logger("structlog").info("[TNC] Starting TCP/IP socket", port=static.PORT)
# https://stackoverflow.com/a/16641793
@ -157,6 +149,6 @@ if __name__ == '__main__':
except Exception as e:
structlog.get_logger("structlog").error("[TNC] Starting TCP/IP socket failed", port=static.PORT, e=e)
os._exit(1)
sys.exit(1)
while 1:
time.sleep(1)

View file

@ -5,51 +5,55 @@ Created on Wed Dec 23 07:04:24 2020
@author: DJ2LS
"""
import sys
import os
import ctypes
from ctypes import *
import pathlib
import logging, structlog, log_handler
import time
import threading
# pylint: disable=invalid-name, line-too-long, c-extension-no-member
# pylint: disable=import-outside-toplevel
import atexit
import numpy as np
import helpers
import static
import data_handler
import ujson as json
import sock
import re
import ctypes
import logging
import os
import pathlib
import queue
import codec2
import audio
import sounddevice as sd
import re
import sys
import threading
import time
from collections import deque
import numpy as np
import sounddevice as sd
import structlog
import ujson as json
# init FIFO queue to store received frames in
import audio
import codec2
import data_handler
import helpers
import log_handler
import sock
import static
TESTMODE = False
RXCHANNEL = ''
TXCHANNEL = ''
# Initialize FIFO queue to store received frames
MODEM_RECEIVED_QUEUE = queue.Queue()
MODEM_TRANSMIT_QUEUE = queue.Queue()
static.TRANSMITTING = False
# receive only specific modes to reduce cpu load
# Receive only specific modes to reduce CPU load
RECEIVE_DATAC1 = False
RECEIVE_DATAC3 = False
RECEIVE_FSK_LDPC_1 = False
class RF():
""" """
def __init__(self):
self.sampler_avg = 0
self.buffer_avg = 0
self.AUDIO_SAMPLE_RATE_RX = 48000
self.AUDIO_SAMPLE_RATE_TX = 48000
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
@ -58,65 +62,64 @@ class RF():
self.AUDIO_CHUNKS = 48 # 8 * (self.AUDIO_SAMPLE_RATE_RX/self.MODEM_SAMPLE_RATE) #48
self.AUDIO_CHANNELS = 1
# locking state for mod out so buffer will be filled before we can use it
# Locking state for mod out so buffer will be filled before we can use it
# https://github.com/DJ2LS/FreeDATA/issues/127
# https://github.com/DJ2LS/FreeDATA/issues/99
self.mod_out_locked = True
# make sure our resampler will work
# Make sure our resampler will work
assert (self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48
# small hack for initializing codec2 via codec2.py module
# TODO: we need to change the entire modem module to integrate codec2 module
# Small hack for initializing codec2 via codec2.py module
# TODO: Need to change the entire modem module to integrate codec2 module
self.c_lib = codec2.api
self.resampler = codec2.resampler()
self.modem_transmit_queue = MODEM_TRANSMIT_QUEUE
self.modem_received_queue = MODEM_RECEIVED_QUEUE
# init FIFO queue to store modulation out in
# Init FIFO queue to store modulation out in
self.modoutqueue = deque()
# define fft_data buffer
# Define fft_data buffer
self.fft_data = bytes()
# open codec2 instance
self.datac0_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), c_void_p)
self.c_lib.freedv_set_tuning_range(self.datac0_freedv, c_float(static.TUNING_RANGE_FMIN), c_float(static.TUNING_RANGE_FMAX))
# 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)
self.datac0_bytes_out = create_string_buffer(self.datac0_bytes_per_frame)
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.datac1_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), c_void_p)
self.c_lib.freedv_set_tuning_range(self.datac1_freedv, c_float(static.TUNING_RANGE_FMIN), c_float(static.TUNING_RANGE_FMAX))
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 = create_string_buffer(self.datac1_bytes_per_frame)
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.datac3_freedv = cast(codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), c_void_p)
self.c_lib.freedv_set_tuning_range(self.datac3_freedv, c_float(static.TUNING_RANGE_FMIN), c_float(static.TUNING_RANGE_FMAX))
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 = create_string_buffer(self.datac3_bytes_per_frame)
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.fsk_ldpc_freedv_0 = cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC, ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV)), c_void_p)
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 = create_string_buffer(self.fsk_ldpc_bytes_per_frame_0)
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 = cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC, ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV)), c_void_p)
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 = create_string_buffer(self.fsk_ldpc_bytes_per_frame_1)
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)
@ -127,66 +130,47 @@ class RF():
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)
# --------------------------------------------CREATE PYAUDIO INSTANCE
'''
if not TESTMODE:
try:
# we need to "try" this, because sometimes libasound.so isn't in the default place
# try to supress error messages
with audio.noalsaerr(): # https://github.com/DJ2LS/FreeDATA/issues/22
self.p = audio.pyaudio.PyAudio()
# else do it the default way
except:
self.p = audio.pyaudio.PyAudio()
atexit.register(self.p.terminate)
# --------------------------------------------OPEN RX AUDIO CHANNEL
# optional auto selection of loopback device if using in testmode
if static.AUDIO_INPUT_DEVICE == -2:
loopback_list = []
for dev in range(0,self.p.get_device_count()):
if 'Loopback: PCM' in self.p.get_device_info_by_index(dev)["name"]:
loopback_list.append(dev)
if len(loopback_list) >= 2:
static.AUDIO_INPUT_DEVICE = loopback_list[0] #0 = RX
static.AUDIO_OUTPUT_DEVICE = loopback_list[1] #1 = TX
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
'''
try:
'''
self.audio_stream = self.p.open(format=audio.pyaudio.paInt16,
channels=self.AUDIO_CHANNELS,
rate=self.AUDIO_SAMPLE_RATE_RX,
frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER_RX,
input=True,
output=True,
input_device_index=static.AUDIO_INPUT_DEVICE,
output_device_index=static.AUDIO_OUTPUT_DEVICE,
stream_callback=self.audio_callback
)
'''
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("opened audio devices")
structlog.get_logger("structlog").info("[MDM] init: opened audio devices")
except Exception as e:
structlog.get_logger("structlog").error("can't open audio device. Exit", e=e)
os._exit(1)
structlog.get_logger("structlog").error("[MDM] init: can't open audio device. Exit", e=e)
sys.exit(1)
try:
structlog.get_logger("structlog").debug("[TNC] starting pyaudio callback")
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("[TNC] 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
self.stream = Object()
self.stream.active = True
# create mkfifo buffer
try:
os.mkfifo(RXCHANNEL)
os.mkfifo(TXCHANNEL)
except Exception as e:
structlog.get_logger("structlog").error(f"[MDM] init:mkfifo: Exception: {e}")
pass
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)
mkfifo_read_callback_thread.start()
# --------------------------------------------INIT AND OPEN HAMLIB
# check how we want to control the radio
# Check how we want to control the radio
if static.HAMLIB_RADIOCONTROL == 'direct':
import rig
elif static.HAMLIB_RADIOCONTROL == 'rigctl':
@ -198,7 +182,6 @@ class RF():
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_RGICTLD_IP, rigctld_port = static.HAMLIB_RGICTLD_PORT)
@ -233,55 +216,95 @@ class RF():
worker_transmit.start()
# --------------------------------------------------------------------------------------------------------
#def audio_callback(self, data_in48k, frame_count, time_info, status):
def mkfifo_read_callback(self):
while 1:
time.sleep(0.01)
# -----read
data_in48k = bytes()
with open(RXCHANNEL, 'rb') as fifo:
for line in fifo:
data_in48k += line
while len(data_in48k) >= 48:
x = np.frombuffer(data_in48k[:48], dtype=np.int16)
x = self.resampler.resample48_to_8(x)
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)
def mkfifo_write_callback(self):
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)
pass
else:
data_out48k = self.modoutqueue.popleft()
#print(len(data_out48k))
fifo_write = open(TXCHANNEL, 'wb')
fifo_write.write(data_out48k)
fifo_write.flush()
# --------------------------------------------------------------------
def callback(self, data_in48k, outdata, frames, time, status):
"""
Args:
data_in48k:
frame_count:
time_info:
data_in48k: Incoming data received
outdata: Container for the data returned
frames: Number of frames
time:
status:
Returns:
Nothing
"""
x = np.frombuffer(data_in48k, dtype=np.int16)
x = self.resampler.resample48_to_8(x)
length_x = len(x)
# avoid decoding when transmitting to reduce CPU
# Avoid decoding when transmitting to reduce CPU
if not static.TRANSMITTING:
# avoid buffer overflow by filling only if buffer not full
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 not full and selected datachannel mode
# 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:
static.BUFFER_OVERFLOW_COUNTER[1] += 1
# avoid buffer overflow by filling only if buffer not full and selected datachannel mode
# 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:
static.BUFFER_OVERFLOW_COUNTER[2] += 1
# avoid buffer overflow by filling only if buffer not full and selected datachannel mode
# 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:
static.BUFFER_OVERFLOW_COUNTER[3] += 1
# avoid buffer overflow by filling only if buffer not full and selected datachannel mode
# 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)
@ -292,21 +315,19 @@ class RF():
# if not self.modoutqueue or self.mod_out_locked:
data_out48k = np.zeros(frames, dtype=np.int16)
self.fft_data = x
else:
data_out48k = self.modoutqueue.popleft()
self.fft_data = data_out48k
try:
outdata[:] = data_out48k[:frames]
except Exception as e:
print(e)
except IndexError as e:
structlog.get_logger("structlog").debug(f"[MDM] callback: IndexError: {e}")
# return (data_out48k, audio.pyaudio.paContinue)
# --------------------------------------------------------------------------------------------------------
def transmit(self, mode, repeats, repeat_delay, frames):
# --------------------------------------------------------------------
def transmit(self, mode, repeats: int, repeat_delay: int, frames: bytearray):
"""
Args:
@ -318,112 +339,99 @@ class RF():
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
# Toggle ptt early to save some time and send ptt state via socket
static.PTT_STATE = self.hamlib.set_ptt(True)
jsondata = {"ptt":"True"}
data_out = json.dumps(jsondata)
sock.SOCKET_QUEUE.put(data_out)
# open codec2 instance
# Open codec2 instance
self.MODE = mode
if self.MODE == 'FSK_LDPC_0' or self.MODE == 200:
freedv = cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC, ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV)), c_void_p)
elif self.MODE == 'FSK_LDPC_1' or self.MODE == 201:
freedv = cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC, ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV)), c_void_p)
freedv = open_codec2_instance(self.MODE)
else:
freedv = cast(codec2.api.freedv_open(self.MODE), c_void_p)
# get number of bytes per frame for mode
# Get number of bytes per frame for mode
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
payload_bytes_per_frame = bytes_per_frame - 2
# init buffer for data
# Init buffer for data
n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv)
mod_out = create_string_buffer(n_tx_modem_samples * 2)
mod_out = ctypes.create_string_buffer(n_tx_modem_samples * 2)
# init buffer for preample
# Init buffer for preample
n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(freedv)
mod_out_preamble = create_string_buffer(n_tx_preamble_modem_samples * 2)
mod_out_preamble = ctypes.create_string_buffer(n_tx_preamble_modem_samples * 2)
# init buffer for postamble
# Init buffer for postamble
n_tx_postamble_modem_samples = codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv)
mod_out_postamble = create_string_buffer(n_tx_postamble_modem_samples * 2)
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 #miliseconds
# Add empty data to handle ptt toggle time
data_delay_mseconds = 0 # milliseconds
data_delay = int(self.MODEM_SAMPLE_RATE * (data_delay_mseconds / 1000))
mod_out_silence = create_string_buffer(data_delay*2)
mod_out_silence = ctypes.create_string_buffer(data_delay * 2)
txbuffer = bytes(mod_out_silence)
structlog.get_logger("structlog").debug("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 i in range(0,repeats):
for _ in range(repeats):
# codec2 fsk preamble may be broken - at least it sounds like that so we are disabling it for testing
if not self.MODE == 'FSK_LDPC_0' or self.MODE == 200 or self.MODE == 'FSK_LDPC_1' or self.MODE == 201:
# write preamble to txbuffer
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(0,len(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
# 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 modulaton for n frames in list
for n in range(len(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
# 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
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
txbuffer += bytes(mod_out)
# codec2 fsk preamble may be broken - at least it sounds like that so we are disabling it for testing
if not self.MODE == 'FSK_LDPC_0' or self.MODE == 200 or self.MODE == 'FSK_LDPC_1' or self.MODE == 201:
# write preamble to txbuffer
# 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)
# append postamble to txbuffer
# add delay to end of frames
# Add delay to end of frames
samples_delay = int(self.MODEM_SAMPLE_RATE * (repeat_delay / 1000))
mod_out_silence = create_string_buffer(samples_delay*2)
mod_out_silence = ctypes.create_string_buffer(samples_delay * 2)
txbuffer += bytes(mod_out_silence)
# resample up to 48k (resampler works on np.int16)
# Re-sample back up to 48k (resampler works on np.int16)
x = np.frombuffer(txbuffer, dtype=np.int16)
x = set_audio_volume(x, static.TX_AUDIO_LEVEL)
txbuffer_48k = self.resampler.resample8_to_48(x)
# explicitly lock our usage of mod_out_queue if needed
# deaktivated for testing purposes
# Explicitly lock our usage of mod_out_queue if needed
# Deaktivated 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)]
for c in chunk:
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)
#structlog.get_logger("structlog").debug("[TNC] mod out shorter than audio buffer", delta=delta)
self.modoutqueue.append(c)
# Release our mod_out_lock so we can use the queue
self.mod_out_locked = False
@ -432,12 +440,12 @@ class RF():
static.PTT_STATE = self.hamlib.set_ptt(False)
# push ptt state to socket stream
# Push ptt state to socket stream
jsondata = {"ptt":"False"}
data_out = json.dumps(jsondata)
sock.SOCKET_QUEUE.put(data_out)
# after processing we want to set the locking state back to true to be prepared for next transmission
# After processing, set the locking state back to true to be prepared for next transmission
self.mod_out_locked = True
self.c_lib.freedv_close(freedv)
@ -520,19 +528,16 @@ class RF():
#self.get_scatter(self.fsk_ldpc_freedv_1)
self.calculate_snr(self.fsk_ldpc_freedv_1)
# worker for FIFO queue for processing received frames
def worker_transmit(self):
""" """
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()
# worker for FIFO queue for processing received frames
def worker_received(self):
""" """
@ -544,7 +549,6 @@ class RF():
data_handler.DATA_QUEUE_RECEIVED.put([data[0], data[1], data[2]])
self.modem_received_queue.task_done()
def get_frequency_offset(self, freedv):
"""
@ -554,14 +558,13 @@ class RF():
Returns:
"""
modemStats = MODEMSTATS()
modemStats = codec2.MODEMSTATS()
self.c_lib.freedv_get_modem_extended_stats.restype = None
self.c_lib.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats))
offset = round(modemStats.foff) * (-1)
static.FREQ_OFFSET = offset
return offset
def get_scatter(self, freedv):
"""
@ -571,7 +574,9 @@ class RF():
Returns:
"""
if static.ENABLE_SCATTER:
if not static.ENABLE_SCATTER:
return
modemStats = codec2.MODEMSTATS()
self.c_lib.freedv_get_modem_extended_stats.restype = None
self.c_lib.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats))
@ -596,7 +601,6 @@ class RF():
scatterdata_small = scatterdata[::10]
static.SCATTER = scatterdata_small
def calculate_snr(self, freedv):
"""
@ -606,20 +610,21 @@ class RF():
Returns:
"""
try:
modem_stats_snr = c_float()
modem_stats_sync = c_int()
modem_stats_snr = ctypes.c_float()
modem_stats_sync = ctypes.c_int()
self.c_lib.freedv_get_modem_stats(freedv, byref(modem_stats_sync), 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)
print(snr)
structlog.get_logger("structlog").info("[MDM] calculate_snr: ", snr=snr)
# print(snr)
static.SNR = np.clip(snr, 0, 255) # limit to max value of 255
return static.SNR
except:
except Exception as e:
structlog.get_logger("structlog").error(f"[MDM] calculate_snr: Exception: {e}")
static.SNR = 0
return static.SNR
@ -633,7 +638,6 @@ class RF():
static.HAMLIB_MODE = self.hamlib.get_mode()
static.HAMLIB_BANDWITH = self.hamlib.get_bandwith()
def calculate_fft(self):
""" """
# channel_busy_delay counter
@ -645,11 +649,9 @@ class RF():
# WE NEED TO OPTIMIZE THIS!
if len(self.fft_data) >= 128:
# https://gist.github.com/ZWMiller/53232427efc5088007cab6feee7c6e4c
# Fast Fourier Transform, 10*log10(abs) is to scale it to dB
# and make sure it's not imaginary
try:
fftarray = np.fft.rfft(self.fft_data)
@ -660,32 +662,25 @@ class RF():
# 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 transmittig 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
# calculate audio max value
# 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
# 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 np.sum(dfft[dfft > avg + 10]) >= 300 and not static.TRANSMITTING:
static.CHANNEL_BUSY = True
channel_busy_delay += 5
# limit delay counter to a maximun of 30. The higher this value, the linger we will wait until releasing state
if channel_busy_delay > 50:
channel_busy_delay = 50
# Limit delay counter to a maximun of 30. The higher this value, the linger 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 -= 1
if channel_busy_delay < 0:
channel_busy_delay = 0
# if our channel busy counter reached 0, we toggle state to False
# 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
if channel_busy_delay == 0:
static.CHANNEL_BUSY = False
@ -693,16 +688,12 @@ class RF():
dfft = np.around(dfft, 0)
dfftlist = dfft.tolist()
static.FFT = dfftlist[0:320] #320 --> bandwith 3000
except:
structlog.get_logger("structlog").debug("[TNC] Setting fft=0")
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").debug("[MDM] Setting fft=0")
# else 0
static.FFT = [0]
else:
pass
def set_frames_per_burst(self, n_frames_per_burst):
"""
@ -717,6 +708,17 @@ class RF():
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)
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)
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):
@ -729,12 +731,7 @@ def get_bytes_per_frame(mode):
Returns:
"""
if mode == 200:
freedv = cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC, ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV)), c_void_p)
elif mode == 201:
freedv = cast(codec2.api.freedv_open_advanced(codec2.api.FREEDV_MODE_FSK_LDPC, ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV)), c_void_p)
else:
freedv = cast(codec2.api.freedv_open(mode), c_void_p)
freedv = open_codec2_instance(mode)
# get number of bytes per frame for mode
return int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
@ -743,5 +740,3 @@ def get_bytes_per_frame(mode):
def set_audio_volume(datalist, volume):
data = np.fromstring(datalist, np.int16) * (volume / 100.)
return data.astype(np.int16)

View file

@ -7,18 +7,15 @@ import atexit
import subprocess
import os
# set global hamlib version
hamlib_version = 0
# append local search path
# check if we are running in a pyinstaller environment
try:
app_path = sys._MEIPASS
except:
app_path = os.path.abspath(".")
sys.path.append(app_path)
if hasattr(sys, "_MEIPASS"):
sys.path.append(getattr(sys, "_MEIPASS"))
else:
sys.path.append(os.path.abspath("."))
# try importing hamlib
try:
@ -76,7 +73,6 @@ except Exception as e:
class radio:
""" """
def __init__(self):
self.devicename = ''
self.devicenumber = ''
self.deviceport = ''
@ -106,7 +102,6 @@ class radio:
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
@ -116,7 +111,6 @@ class radio:
self.stop_bits = str(stop_bits)
self.handshake = str(handshake)
# try to init hamlib
try:
Hamlib.rig_set_debug(Hamlib.RIG_DEBUG_NONE)
@ -128,7 +122,6 @@ class radio:
structlog.get_logger("structlog").error("[RIG] Hamlib: rig not supported...")
self.devicenumber = 0
self.my_rig = Hamlib.Rig(self.devicenumber)
self.my_rig.set_conf("rig_pathname", self.deviceport)
self.my_rig.set_conf("retry", "5")
@ -138,9 +131,6 @@ 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':
self.hamlib_ptt_type = Hamlib.RIG_PTT_RIG
self.my_rig.set_conf("ptt_type", 'RIG')
@ -149,7 +139,6 @@ class radio:
self.hamlib_ptt_type = Hamlib.RIG_PORT_USB
self.my_rig.set_conf("ptt_type", 'USB')
elif self.hamlib_ptt_type == 'DTR-H':
self.hamlib_ptt_type = Hamlib.RIG_PTT_SERIAL_DTR
self.my_rig.set_conf("dtr_state", "HIGH")
@ -182,7 +171,6 @@ class radio:
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.my_rig.open()
atexit.register(self.my_rig.close)
@ -199,7 +187,6 @@ class radio:
except:
structlog.get_logger("structlog").info("[RIG] Hamlib device opened", status='SUCCESS')
# set ptt to false if ptt is stuck for some reason
self.set_ptt(False)

View file

@ -2,26 +2,27 @@
# Intially created by Franco Spinelli, IW2DHW, 01/2022
# Updated by DJ2LS
#
#
# versione mia di rig.py per gestire Ft897D tramite rigctl e senza
# fare alcun riferimento alla configurazione
#
# e' una pezza clamorosa ma serve per poter provare on-air il modem
#
import subprocess
import structlog
import time
import sys
import os
import subprocess
import sys
import time
import structlog
# for rig_model -> rig_number only
# set global hamlib version
hamlib_version = 0
class radio:
""" """
def __init__(self):
self.devicename = ''
self.devicenumber = ''
self.deviceport = ''
@ -51,7 +52,6 @@ class radio:
Returns:
"""
self.devicename = devicename
self.deviceport = deviceport
self.serialspeed = str(serialspeed) # we need to ensure this is a str, otherwise set_conf functions are crashing
@ -61,24 +61,22 @@ class radio:
self.stop_bits = stop_bits
self.handshake = handshake
# check if we are running in a pyinstaller environment
try:
app_path = sys._MEIPASS
except:
app_path = os.path.abspath(".")
sys.path.append(app_path)
if hasattr(sys, "_MEIPASS"):
sys.path.append(getattr(sys, "_MEIPASS"))
else:
sys.path.append(os.path.abspath("."))
# get devicenumber by looking for deviceobject in Hamlib module
try:
import Hamlib
self.devicenumber = int(getattr(Hamlib, self.devicename))
except:
except Exception as e:
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)
structlog.get_logger("structlog").warning("[RIGCTL] Radio not found. Using DUMMY!", error=e)
# set deviceport to dummy port, if we selected dummy model
if self.devicenumber == 1 or self.devicenumber == 6:
@ -86,12 +84,9 @@ class radio:
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 == 'win32' or sys.platform == 'win64':
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))
else:
@ -116,7 +111,6 @@ class radio:
except:
return False
def get_mode(self):
""" """
#(hamlib_mode, bandwith) = self.my_rig.get_mode()
@ -126,7 +120,6 @@ class radio:
except:
return False
def get_bandwith(self):
""" """
#(hamlib_mode, bandwith) = self.my_rig.get_mode()

View file

@ -1,20 +1,24 @@
#!/usr/bin/env python3
import socket
import structlog
import log_handler
import logging
import time
import static
# class taken from darsidelemm
# rigctl - https://github.com/darksidelemm/rotctld-web-gui/blob/master/rotatorgui.py#L35
#
# 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():
"""rotctld (hamlib) communication class"""
"""rigctld (hamlib) communication class"""
# Note: This is a massive hack.
def __init__(self, hostname="localhost", port=4532, poll_rate=5, timeout=5):
@ -48,11 +52,10 @@ class radio():
self.hostname = rigctld_ip
self.port = int(rigctld_port)
if self.connect():
logging.debug(f"Rigctl intialized")
logging.debug("Rigctl intialized")
return True
else:
structlog.get_logger("structlog").error("[RIGCTLD] Can't connect to rigctld!", ip=self.hostname, port=self.port)
return False
@ -75,7 +78,6 @@ class radio():
self.sock.close()
self.connected = False
def send_command(self, command):
"""Send a command to the connected rotctld instance,
and return the return value.
@ -104,7 +106,6 @@ class radio():
time.sleep(0.5)
self.connect()
def get_mode(self):
""" """
try:
@ -113,7 +114,8 @@ class radio():
mode = data[0]
return mode.decode("utf-8")
except:
0
return 0
def get_bandwith(self):
""" """
try:

View file

@ -4,12 +4,12 @@ import structlog
hamlib_version = 0
class radio:
""" """
def __init__(self):
pass
def open_rig(self, **kwargs):
"""
@ -62,4 +62,3 @@ class radio:
def close_rig(self):
""" """
return

View file

@ -19,21 +19,25 @@ Created on Fri Dec 25 21:25:14 2020
# "data" : "..."
"""
import atexit
import base64
import logging
import os
import queue
import socketserver
import sys
import threading
import ujson as json
import time
import static
import psutil
import structlog
import ujson as json
import audio
import data_handler
import helpers
import sys
import os
import logging, structlog, log_handler
import queue
import psutil
import audio
import base64
import atexit
import log_handler
import static
SOCKET_QUEUE = queue.Queue()
DAEMON_QUEUE = queue.Queue()
@ -42,10 +46,6 @@ CONNECTED_CLIENTS = set()
CLOSE_SIGNAL = False
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
"""
the socket handler base class
@ -53,23 +53,19 @@ class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
""" """
connection_alive = False
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:
@ -82,7 +78,6 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
SOCKET_QUEUE.put(data)
time.sleep(0.5)
while not SOCKET_QUEUE.empty():
data = SOCKET_QUEUE.get()
sock_data = bytes(data, 'utf-8')
@ -94,8 +89,8 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
try:
client.send(sock_data)
except Exception as e:
print("connection lost...")
print(e)
# print("connection lost...")
structlog.get_logger("structlog").info("[SCK] Connection lost", e=e)
self.connection_alive = False
# we want to transmit scatter data only once to reduce network traffic
@ -120,7 +115,6 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
#print("connection broken. Closing...")
self.connection_alive = False
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')
@ -141,19 +135,16 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
# and which one can be processed during a running transmission
time.sleep(3)
# 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)
self.connection_alive = False
def handle(self):
"""
socket handler
"""
CONNECTED_CLIENTS.add(self.request)
structlog.get_logger("structlog").debug("[SCK] Client connected", ip=self.client_address[0], port=self.client_address[1])
@ -166,8 +157,6 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
while self.connection_alive and not CLOSE_SIGNAL:
time.sleep(1)
def finish(self):
""" """
structlog.get_logger("structlog").warning("[SCK] Closing client socket", ip=self.client_address[0], port=self.client_address[1])
@ -176,7 +165,6 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
except:
structlog.get_logger("structlog").warning("[SCK] client connection already removed from client list", client=self.request)
def process_tnc_commands(data):
"""
process tnc commands
@ -189,7 +177,6 @@ def process_tnc_commands(data):
"""
# 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)
@ -203,7 +190,6 @@ def process_tnc_commands(data):
command_response("tx_audio_level", False)
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
# TRANSMIT SINE WAVE -----------------------------------------------------
if received_json["type"] == "set" and received_json["command"] == "send_test_frame":
try:
@ -222,6 +208,7 @@ def process_tnc_commands(data):
except Exception as e:
command_response("cqcqcq", False)
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
# START_BEACON -----------------------------------------------------
if received_json["command"] == "start_beacon":
try:
@ -262,7 +249,6 @@ def process_tnc_commands(data):
command_response("ping", False)
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
# CONNECT ----------------------------------------------------------
if received_json["type"] == 'arq' and received_json["command"] == "connect":
static.BEACON_PAUSE = True
@ -297,7 +283,6 @@ def process_tnc_commands(data):
# TRANSMIT RAW DATA -------------------------------------------
if received_json["type"] == 'arq' and received_json["command"] == "send_raw":
static.BEACON_PAUSE = True
try:
if not static.ARQ_SESSION:
@ -314,7 +299,6 @@ def process_tnc_commands(data):
dxcallsign = static.DXCALLSIGN
static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN)
mode = int(received_json["parameter"][0]["mode"])
n_frames = int(received_json["parameter"][0]["n_frames"])
base64data = received_json["parameter"][0]["data"]
@ -335,15 +319,12 @@ def process_tnc_commands(data):
binarydata = base64.b64decode(base64data)
data_handler.DATA_QUEUE_TRANSMIT.put(['ARQ_RAW', binarydata, mode, n_frames, arq_uuid, mycallsign])
else:
raise TypeError
except Exception as e:
command_response("send_raw", False)
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
# STOP TRANSMISSION ----------------------------------------------------------
if received_json["type"] == 'arq' and received_json["command"] == "stop_transmission":
try:
@ -357,7 +338,6 @@ def process_tnc_commands(data):
command_response("stop_transmission", False)
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
if received_json["type"] == 'get' and received_json["command"] == 'rx_buffer':
try:
output = {
@ -365,7 +345,7 @@ def process_tnc_commands(data):
"data-array": [],
}
for i in range(0, len(static.RX_BUFFER)):
for i in range(len(static.RX_BUFFER)):
#print(static.RX_BUFFER[i][4])
#rawdata = json.loads(static.RX_BUFFER[i][4])
base64_data = static.RX_BUFFER[i][4]
@ -380,7 +360,6 @@ def process_tnc_commands(data):
command_response("rx_buffer", False)
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
if received_json["type"] == 'set' and received_json["command"] == 'del_rx_buffer':
try:
static.RX_BUFFER = []
@ -397,7 +376,6 @@ def send_tnc_state():
"""
send the tnc state to network
"""
encoding = 'utf-8'
output = {
@ -432,12 +410,17 @@ def send_tnc_state():
}
# add heard stations to heard stations object
for i in range(0, len(static.HEARD_STATIONS)):
output["stations"].append({"dxcallsign": str(static.HEARD_STATIONS[i][0], 'utf-8'), "dxgrid": str(static.HEARD_STATIONS[i][1], 'utf-8'),"timestamp": static.HEARD_STATIONS[i][2], "datatype": static.HEARD_STATIONS[i][3], "snr": static.HEARD_STATIONS[i][4], "offset": static.HEARD_STATIONS[i][5], "frequency": static.HEARD_STATIONS[i][6]})
jsondata = json.dumps(output)
return jsondata
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 process_daemon_commands(data):
"""
@ -469,7 +452,6 @@ def process_daemon_commands(data):
command_response("mycallsign", False)
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
if received_json["type"] == 'set' and received_json["command"] == 'mygrid':
try:
mygrid = received_json["parameter"]
@ -484,9 +466,7 @@ def process_daemon_commands(data):
command_response("mygrid", False)
structlog.get_logger("structlog").warning("[SCK] command execution error", e=e, command=received_json)
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"])
@ -512,35 +492,34 @@ def process_daemon_commands(data):
tx_audio_level = str(received_json["parameter"][0]["tx_audio_level"])
respond_to_cq = str(received_json["parameter"][0]["respond_to_cq"])
# 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])
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)
@ -549,7 +528,6 @@ def process_daemon_commands(data):
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':
try:
devicename = str(received_json["parameter"][0]["devicename"])
deviceport = str(received_json["parameter"][0]["deviceport"])
@ -563,18 +541,18 @@ 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:
@ -599,7 +577,7 @@ def send_daemon_state():
send the daemon state to network
"""
try:
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])}"
output = {
'command': 'daemon_state',
@ -619,7 +597,6 @@ def send_daemon_state():
else:
output["daemon_state"].append({"status": "stopped"})
jsondata = json.dumps(output)
return jsondata
@ -628,11 +605,7 @@ def send_daemon_state():
return None
def command_response(command, status):
if status:
status = "OK"
else:
status = "Failed"
jsondata = {"command_response": command, "status" : status}
s_status = "OK" if status else "Failed"
jsondata = {"command_response": command, "status" : s_status}
data_out = json.dumps(jsondata)
SOCKET_QUEUE.put(data_out)

View file

@ -15,7 +15,6 @@ DAEMONPORT = 3001
TNCSTARTED = False
TNCPROCESS = 0
# Operator Defaults
MYCALLSIGN = b'AA0AA'
MYCALLSIGN_CRC = b'A'
@ -39,7 +38,6 @@ SOCKET_TIMEOUT = 1 # seconds
SERIAL_DEVICES = []
# ---------------------------------
PTT_STATE = False
TRANSMITTING = False
@ -96,7 +94,6 @@ ARQ_TRANSMISSION_PERCENT = 0
ARQ_SPEED_LEVEL = 0
TOTAL_BYTES = 0
#CHANNEL_STATE = 'RECEIVING_SIGNALLING'
TNC_STATE = 'IDLE'
ARQ_STATE = False