From ec13228efbf52b7aab022c85b8b7a320b67c4875 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sun, 31 Dec 2023 13:36:37 +0100 Subject: [PATCH] cemetry cleanup --- modem/data cemetery/CMakeLists.txt | 218 --- modem/data cemetery/deprecated_daemon.py | 630 ------- .../data cemetery/deprecated_data_handler.py | 35 - .../deprecated_data_handler_broadcasts.py | 194 --- ...deprecated_data_handler_data_broadcasts.py | 122 -- .../deprecated_data_handler_ping.py | 145 -- modem/data cemetery/deprecated_main.py | 253 --- .../deprecated_protocol_arq_connection.py | 309 ---- .../deprecated_protocol_arq_session.py | 736 --------- .../deprecated_protocol_arq_session_irs.py | 853 ---------- .../deprecated_protocol_arq_session_iss.py | 938 ----------- modem/data cemetery/deprecated_sock.py | 1469 ----------------- .../001_highsnr_stdio_audio/test_virtual3.sh | 23 - .../test/002_highsnr_ping_pong/README.md | 103 -- modem/data cemetery/test/README.md | 165 -- modem/data cemetery/test/hamlib-test.py | 121 -- modem/data cemetery/test/ping.py | 214 --- modem/data cemetery/test/pong.py | 166 -- modem/data cemetery/test/test_audiobuffer.py | 88 - modem/data cemetery/test/test_chat_text.py | 237 --- modem/data cemetery/test/test_datac13.py | 291 ---- .../test/test_datac13_negative.py | 261 --- modem/data cemetery/test/test_helpers.py | 98 -- .../test/test_highsnr_stdio_C_P_datacx.py | 192 --- .../test/test_highsnr_stdio_P_C_datacx.py | 192 --- .../test/test_highsnr_stdio_P_P_datacx.py | 153 -- .../test/test_highsnr_stdio_P_P_multi.py | 148 -- modem/data cemetery/test/test_modem.py | 71 - modem/data cemetery/test/test_modem_states.py | 326 ---- modem/data cemetery/test/test_pa.py | 48 - .../data cemetery/test/test_resample_48_8.py | 120 -- modem/data cemetery/test/test_virtual1.sh | 27 - modem/data cemetery/test/test_virtual1a.sh | 16 - modem/data cemetery/test/test_virtual1b.sh | 15 - modem/data cemetery/test/test_virtual1c.sh | 15 - modem/data cemetery/test/test_virtual2.sh | 23 - modem/data cemetery/test/test_virtual3a.sh | 23 - modem/data cemetery/test/test_virtual3b.sh | 23 - modem/data cemetery/test/test_virtual4a.sh | 32 - modem/data cemetery/test/test_virtual4b.sh | 32 - modem/data cemetery/test/test_virtual_mm.sh | 32 - .../test/util_callback_multimode_rx.py | 323 ---- .../util_callback_multimode_rx_outside.py | 311 ---- .../test/util_callback_multimode_tx.py | 256 --- modem/data cemetery/test/util_callback_rx.py | 219 --- .../test/util_callback_rx_outside.py | 217 --- modem/data cemetery/test/util_callback_tx.py | 272 --- modem/data cemetery/test/util_chat_text_1.py | 219 --- modem/data cemetery/test/util_chat_text_2.py | 163 -- modem/data cemetery/test/util_datac13.py | 309 ---- .../test/util_datac13_negative.py | 311 ---- modem/data cemetery/test/util_modem_IRS.py | 108 -- modem/data cemetery/test/util_modem_ISS.py | 156 -- modem/data cemetery/test/util_multimode_rx.py | 254 --- modem/data cemetery/test/util_multimode_tx.py | 199 --- modem/data cemetery/test/util_rx.py | 253 --- modem/data cemetery/test/util_tx.py | 229 --- modem/lib/libcodec2 | 1 - 58 files changed, 12957 deletions(-) delete mode 100644 modem/data cemetery/CMakeLists.txt delete mode 100755 modem/data cemetery/deprecated_daemon.py delete mode 100644 modem/data cemetery/deprecated_data_handler.py delete mode 100644 modem/data cemetery/deprecated_data_handler_broadcasts.py delete mode 100644 modem/data cemetery/deprecated_data_handler_data_broadcasts.py delete mode 100644 modem/data cemetery/deprecated_data_handler_ping.py delete mode 100755 modem/data cemetery/deprecated_main.py delete mode 100644 modem/data cemetery/deprecated_protocol_arq_connection.py delete mode 100644 modem/data cemetery/deprecated_protocol_arq_session.py delete mode 100644 modem/data cemetery/deprecated_protocol_arq_session_irs.py delete mode 100644 modem/data cemetery/deprecated_protocol_arq_session_iss.py delete mode 100644 modem/data cemetery/deprecated_sock.py delete mode 100755 modem/data cemetery/test/001_highsnr_stdio_audio/test_virtual3.sh delete mode 100644 modem/data cemetery/test/002_highsnr_ping_pong/README.md delete mode 100644 modem/data cemetery/test/README.md delete mode 100644 modem/data cemetery/test/hamlib-test.py delete mode 100644 modem/data cemetery/test/ping.py delete mode 100644 modem/data cemetery/test/pong.py delete mode 100644 modem/data cemetery/test/test_audiobuffer.py delete mode 100644 modem/data cemetery/test/test_chat_text.py delete mode 100644 modem/data cemetery/test/test_datac13.py delete mode 100644 modem/data cemetery/test/test_datac13_negative.py delete mode 100644 modem/data cemetery/test/test_helpers.py delete mode 100644 modem/data cemetery/test/test_highsnr_stdio_C_P_datacx.py delete mode 100644 modem/data cemetery/test/test_highsnr_stdio_P_C_datacx.py delete mode 100644 modem/data cemetery/test/test_highsnr_stdio_P_P_datacx.py delete mode 100644 modem/data cemetery/test/test_highsnr_stdio_P_P_multi.py delete mode 100755 modem/data cemetery/test/test_modem.py delete mode 100644 modem/data cemetery/test/test_modem_states.py delete mode 100644 modem/data cemetery/test/test_pa.py delete mode 100644 modem/data cemetery/test/test_resample_48_8.py delete mode 100755 modem/data cemetery/test/test_virtual1.sh delete mode 100755 modem/data cemetery/test/test_virtual1a.sh delete mode 100755 modem/data cemetery/test/test_virtual1b.sh delete mode 100755 modem/data cemetery/test/test_virtual1c.sh delete mode 100755 modem/data cemetery/test/test_virtual2.sh delete mode 100755 modem/data cemetery/test/test_virtual3a.sh delete mode 100755 modem/data cemetery/test/test_virtual3b.sh delete mode 100755 modem/data cemetery/test/test_virtual4a.sh delete mode 100755 modem/data cemetery/test/test_virtual4b.sh delete mode 100755 modem/data cemetery/test/test_virtual_mm.sh delete mode 100644 modem/data cemetery/test/util_callback_multimode_rx.py delete mode 100644 modem/data cemetery/test/util_callback_multimode_rx_outside.py delete mode 100644 modem/data cemetery/test/util_callback_multimode_tx.py delete mode 100644 modem/data cemetery/test/util_callback_rx.py delete mode 100644 modem/data cemetery/test/util_callback_rx_outside.py delete mode 100644 modem/data cemetery/test/util_callback_tx.py delete mode 100644 modem/data cemetery/test/util_chat_text_1.py delete mode 100644 modem/data cemetery/test/util_chat_text_2.py delete mode 100644 modem/data cemetery/test/util_datac13.py delete mode 100644 modem/data cemetery/test/util_datac13_negative.py delete mode 100644 modem/data cemetery/test/util_modem_IRS.py delete mode 100644 modem/data cemetery/test/util_modem_ISS.py delete mode 100755 modem/data cemetery/test/util_multimode_rx.py delete mode 100644 modem/data cemetery/test/util_multimode_tx.py delete mode 100644 modem/data cemetery/test/util_rx.py delete mode 100644 modem/data cemetery/test/util_tx.py delete mode 160000 modem/lib/libcodec2 diff --git a/modem/data cemetery/CMakeLists.txt b/modem/data cemetery/CMakeLists.txt deleted file mode 100644 index e6d53db2..00000000 --- a/modem/data cemetery/CMakeLists.txt +++ /dev/null @@ -1,218 +0,0 @@ -cmake_minimum_required(VERSION 3.0) -project (FreeDATA) -include(CTest) -enable_testing() - -# Find codec2 -if(CODEC2_BUILD_DIR) - find_package(codec2 REQUIRED - PATHS ${CODEC2_BUILD_DIR} - NO_DEFAULT_PATH - CONFIGS codec2.cmake - ) - if(codec2_FOUND) - message(STATUS "Codec2 library found in build tree.") - endif() -else() - find_package(codec2 REQUIRED) -endif() - -# test variables -set(FRAMESPERBURST 3) -set(BURSTS 1) -set(TESTFRAMES 3) - -add_test(NAME audio_buffer - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../modem; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 test_audiobuffer.py") - set_tests_properties(audio_buffer PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") - -add_test(NAME resampler - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../modem; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 test_resample_48_8.py") - set_tests_properties(resampler PROPERTIES PASS_REGULAR_EXPRESSION "PASS") - -add_test(NAME modem_state_machine - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../modem; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 test_modem_states.py") - set_tests_properties(modem_state_machine PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") - -add_test(NAME modem_irs_iss - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../modem; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 test_modem.py") - set_tests_properties(modem_irs_iss PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") - -# disabled this test as its actually broken since we introduced session IDs -#add_test(NAME chat_text -# COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; -# export PYTHONPATH=../modem; -# cd ${CMAKE_CURRENT_SOURCE_DIR}/test; -# python3 test_chat_text.py") -# set_tests_properties(chat_text PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") - -add_test(NAME datac13_frames - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../modem; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 test_datac13.py") - set_tests_properties(datac13_frames PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") - -# disabled this test as its actually broken since we introduced dataclasses -#add_test(NAME datac13_frames_negative -# COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; -# export PYTHONPATH=../modem; -# cd ${CMAKE_CURRENT_SOURCE_DIR}/test; -# python3 test_datac13_negative.py") -# set_tests_properties(datac13_frames_negative PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") - -add_test(NAME helper_routines - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../modem; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 test_helpers.py") - set_tests_properties(helper_routines PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") - -add_test(NAME py_highsnr_stdio_P_P_multi - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../modem; - export BURSTS=${BURSTS}; - export FRAMESPERBURST=${FRAMESPERBURST}; - PATH=$PATH:${CODEC2_BUILD_DIR}/src; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 test_highsnr_stdio_P_P_multi.py") - set_tests_properties(py_highsnr_stdio_P_P_multi PROPERTIES PASS_REGULAR_EXPRESSION "DATAC13: ${BURSTS}/${FRAMESPERBURST} DATAC1: ${BURSTS}/${FRAMESPERBURST} DATAC3: ${BURSTS}/${FRAMESPERBURST}") - -add_test(NAME py_highsnr_stdio_P_P_datacx - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../modem; - export BURSTS=${BURSTS}; - export FRAMESPERBURST=${FRAMESPERBURST}; - PATH=$PATH:${CODEC2_BUILD_DIR}/src; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 test_highsnr_stdio_P_P_datacx.py") - set_tests_properties(py_highsnr_stdio_P_P_datacx PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") - -add_test(NAME py_highsnr_stdio_P_C_datacx - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../modem; - export BURSTS=${BURSTS}; - export FRAMESPERBURST=${FRAMESPERBURST}; - PATH=$PATH:${CODEC2_BUILD_DIR}/src; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 test_highsnr_stdio_P_C_datacx.py") - set_tests_properties(py_highsnr_stdio_P_C_datacx PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD") - -add_test(NAME py_highsnr_stdio_C_P_datacx - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../modem; - export BURSTS=${BURSTS}; - export FRAMESPERBURST=${FRAMESPERBURST}; - export TESTFRAMES=${TESTFRAMES}; - PATH=$PATH:${CODEC2_BUILD_DIR}/src; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 test_highsnr_stdio_C_P_datacx.py") - set_tests_properties(py_highsnr_stdio_C_P_datacx PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") - -add_test(NAME highsnr_stdio_P_C_single - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - PATH=$PATH:${CODEC2_BUILD_DIR}/src; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 util_tx.py --mode datac13 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | - sox -t .s16 -r 48000 -c 1 - -t .s16 -r 8000 -c 1 - | - freedv_data_raw_rx datac13 - - --framesperburst ${FRAMESPERBURST} | hexdump -C") - set_tests_properties(highsnr_stdio_P_C_single PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD") - -add_test(NAME highsnr_stdio_C_P_single - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - PATH=$PATH:${CODEC2_BUILD_DIR}/src; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - freedv_data_raw_tx datac13 --testframes ${TESTFRAMES} --bursts ${BURSTS} --framesperburst ${FRAMESPERBURST} /dev/zero - | - sox -t .s16 -r 8000 -c 1 - -t .s16 -r 48000 -c 1 - | - python3 util_rx.py --mode datac13 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") - set_tests_properties(highsnr_stdio_C_P_single PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") - -add_test(NAME highsnr_stdio_P_P_single - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - PATH=$PATH:${CODEC2_BUILD_DIR}/src; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 util_tx.py --mode datac13 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | - python3 util_rx.py --debug --mode datac13 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") - set_tests_properties(highsnr_stdio_P_P_single PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}") - -add_test(NAME highsnr_stdio_P_P_multi - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - PATH=$PATH:${CODEC2_BUILD_DIR}/src; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 util_multimode_tx.py --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | - python3 util_multimode_rx.py --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} --timeout 60") - set_tests_properties(highsnr_stdio_P_P_multi PROPERTIES PASS_REGULAR_EXPRESSION "DATAC13: ${BURSTS}/${FRAMESPERBURST} DATAC1: ${BURSTS}/${FRAMESPERBURST} DATAC3: ${BURSTS}/${FRAMESPERBURST}") - -# These tests can't run on GitHub actions as we don't have a virtual sound card -if(NOT DEFINED ENV{GITHUB_RUN_ID}) - -# uses aplay/arecord then pipe to Python -add_test(NAME highsnr_virtual1_P_P_single_alsa - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - PATH=$PATH:${CODEC2_BUILD_DIR}/src; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - ./test_virtual1.sh") - set_tests_properties(highsnr_virtual1_P_P_single_alsa PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 5 RECEIVED FRAMES: 10 RX_ERRORS: 0") - -# let Python do audio I/O -add_test(NAME highsnr_virtual2_P_P_single - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - PATH=$PATH:${CODEC2_BUILD_DIR}/src; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - ./test_virtual2.sh") - set_tests_properties(highsnr_virtual2_P_P_single PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 3 RECEIVED FRAMES: 6 RX_ERRORS: 0") - -# Multimode test with Python I/O -add_test(NAME highsnr_virtual3_P_P_multi - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - PATH=$PATH:${CODEC2_BUILD_DIR}/src; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - ./test_virtual_mm.sh") - set_tests_properties(highsnr_virtual3_P_P_multi PROPERTIES PASS_REGULAR_EXPRESSION "DATAC13: 2/4 DATAC1: 2/4 DATAC3: 2/4") - -# let Python do audio I/O via pyaudio callback mode -add_test(NAME highsnr_virtual4_P_P_single_callback - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - PATH=$PATH:${CODEC2_BUILD_DIR}/src; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - ./test_virtual3a.sh") - set_tests_properties(highsnr_virtual4_P_P_single_callback PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 3 RECEIVED FRAMES: 6 RX_ERRORS: 0") - -# let Python do audio I/O via pyaudio callback mode with code outside of callback -add_test(NAME highsnr_virtual4_P_P_single_callback_outside - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - PATH=$PATH:${CODEC2_BUILD_DIR}/src; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - ./test_virtual3b.sh") - set_tests_properties(highsnr_virtual4_P_P_single_callback_outside PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 3 RECEIVED FRAMES: 6 RX_ERRORS: 0") - -# let Python do audio I/O via pyaudio callback mode with code outside of callback -add_test(NAME highsnr_virtual5_P_P_multi_callback - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - PATH=$PATH:${CODEC2_BUILD_DIR}/src; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - ./test_virtual4a.sh") - set_tests_properties(highsnr_virtual5_P_P_multi_callback PROPERTIES PASS_REGULAR_EXPRESSION "DATAC13: 2/4 DATAC1: 2/4 DATAC3: 2/4") - -# let Python do audio I/O via pyaudio callback mode with code outside of callback -add_test(NAME highsnr_virtual5_P_P_multi_callback_outside - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - PATH=$PATH:${CODEC2_BUILD_DIR}/src; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - ./test_virtual4b.sh") - set_tests_properties(highsnr_virtual5_P_P_multi_callback_outside PROPERTIES PASS_REGULAR_EXPRESSION "DATAC13: 2/4 DATAC1: 2/4 DATAC3: 2/4") - -endif() - diff --git a/modem/data cemetery/deprecated_daemon.py b/modem/data cemetery/deprecated_daemon.py deleted file mode 100755 index 6ef6ebb5..00000000 --- a/modem/data cemetery/deprecated_daemon.py +++ /dev/null @@ -1,630 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- -""" -deprecated_daemon.py - -Author: DJ2LS, January 2022 - -daemon for providing basic information for the modem 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 signal -import socketserver -import subprocess -import sys -import threading -import time -import audio -import crcengine -import log_handler -import serial.tools.list_ports -import deprecated_sock -from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem - -import structlog -import ujson as json -import config - -# signal handler for closing application -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 - - """ - - log = structlog.get_logger("DAEMON") - - 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() - - rigctld_watchdog_thread = threading.Thread(target=self.rigctld_watchdog, name="WORKER", daemon=True) - rigctld_watchdog_thread.start() - - - def rigctld_watchdog(self): - """ - Check for rigctld status - Returns: - - """ - while True: - threading.Event().wait(0.01) - - # only continue, if we have a process object initialized - if hasattr(Daemon.rigctldprocess, "returncode"): - - if Daemon.rigctldprocess.returncode in [None, "None"] or not Daemon.rigctldstarted: - Daemon.rigctldstarted = True - # outs, errs = Daemon.rigctldprocess.communicate(timeout=10) - # print(f"outs: {outs}") - # print(f"errs: {errs}") - - else: - self.log.warning("[DMN] [RIGCTLD] [Watchdog] returncode detected",process=Daemon.rigctldprocess) - Daemon.rigctldstarted = False - # triggering another kill - Daemon.rigctldprocess.kill() - # erase process object - Daemon.rigctldprocess = None - else: - Daemon.rigctldstarted = False - - - def update_audio_devices(self): - """ - Update audio devices and set to static - """ - while True: - try: - if not Daemon.modemstarted: - ( - AudioParam.audio_input_devices, - AudioParam.audio_output_devices, - ) = audio.get_audio_devices() - except Exception as err1: - self.log.error( - "[DMN] update_audio_devices: Exception gathering audio devices:", - e=err1, - ) - threading.Event().wait(1) - - def update_serial_devices(self): - """ - Update serial devices and set to static - """ - while True: - try: - 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)} - ) - - Daemon.serial_devices = serial_devices - threading.Event().wait(1) - except Exception as err1: - self.log.error( - "[DMN] update_serial_devices: Exception gathering serial devices:", - e=err1, - ) - - def worker(self): - """ - Worker to handle the received commands - """ - while True: - try: - data = self.daemon_queue.get() - # increase length of list for storing additional - # parameters starting at entry 64 - data = data[:64] + [None] * (64 - len(data)) - # data[1] mycall - # data[2] mygrid - # data[3] rx_audio - # data[4] tx_audio - # data[5] radiocontrol - # data[6] rigctld_ip - # data[7] rigctld_port - # data[8] send_scatter - # data[9] send_fft - # data[10] low_bandwidth_mode - # data[11] tuning_range_fmin - # data[12] tuning_range_fmax - # data[13] enable FSK - # data[14] tx-audio-level - # data[15] respond_to_cq - # data[16] rx_buffer_size - # data[17] explorer - # data[18] ssid_list - # data[19] auto_tune - # data[20] stats - # data[21] tx_delay - - if data[0] == "STARTModem": - self.start_modem(data) - - if data[0] == "TEST_HAMLIB": - # data[9] radiocontrol - # data[10] rigctld_ip - # data[11] rigctld_port - self.test_hamlib_ptt(data) - - if data[0] == "START_RIGCTLD": - """ - data[0] START_RIGCTLD, - data[1] hamlib_deviceid, - data[2] hamlib_deviceport, - data[3] hamlib_stop_bits, - data[4] hamlib_data_bits, - data[5] hamlib_handshake, - data[6] hamlib_serialspeed, - data[7] hamlib_dtrstate, - data[8] hamlib_pttprotocol, - data[9] hamlib_ptt_port, - data[10] hamlib_dcd, - data[11] hamlbib_serialspeed_ptt, - data[12] hamlib_rigctld_port, - data[13] hamlib_rigctld_ip, - data[14] hamlib_rigctld_path, - data[15] hamlib_rigctld_server_port, - data[16] hamlib_rigctld_custom_args - """ - self.start_rigctld(data) - - - except Exception as err1: - self.log.error("[DMN] worker: Exception: ", e=err1) - - def test_hamlib_ptt(self, data): - radiocontrol = data[1] - - # check how we want to control the radio - if radiocontrol == "rigctld": - import rigctld as rig - rigctld_ip = data[2] - rigctld_port = data[3] - - elif radiocontrol == "tci": - import tci as rig - rigctld_ip = data[22] - rigctld_port = data[23] - - else: - import rigdummy as rig - rigctld_ip = '127.0.0.1' - rigctld_port = '0' - - hamlib = rig.radio() - hamlib.open_rig( - rigctld_ip=rigctld_ip, - rigctld_port=rigctld_port, - ) - - # hamlib_version = rig.hamlib_version - - hamlib.set_ptt(True) - #Allow a little time for network based rig to register PTT is active - time.sleep(.250) - if hamlib.get_ptt(): - self.log.info("[DMN] Hamlib PTT", status="SUCCESS") - response = {"command": "test_hamlib", "result": "SUCCESS"} - else: - self.log.warning("[DMN] Hamlib PTT", status="NO SUCCESS") - response = {"command": "test_hamlib", "result": "NOSUCCESS"} - - hamlib.set_ptt(False) - hamlib.close_rig() - - jsondata = json.dumps(response) - sock.SOCKET_QUEUE.put(jsondata) - - def start_rigctld(self, data): - # Seems to be working on Win - """ - data[0] START_RIGCTLD, - data[1] hamlib_deviceid, - data[2] hamlib_deviceport, - data[3] hamlib_stop_bits, - data[4] hamlib_data_bits, - data[5] hamlib_handshake, - data[6] hamlib_serialspeed, - data[7] hamlib_dtrstate, - data[8] hamlib_pttprotocol, - data[9] hamlib_ptt_port, - data[10] hamlib_dcd, - data[11] hamlbib_serialspeed_ptt, - data[12] hamlib_rigctld_port, - data[13] hamlib_rigctld_ip, - data[14] hamlib_rigctld_path, - data[15] hamlib_rigctld_server_port, - data[16] hamlib_rigctld_custom_args - """ - try: - command = [] - - isWin = False - - if sys.platform in ["darwin"]: - if data[14] not in [""]: - # hamlib_rigctld_path - application_path = data[14] - else: - application_path = "rigctld" - - command.append(f'{application_path}') - - elif sys.platform in ["linux", "darwin"]: - if data[14] not in [""]: - # hamlib_rigctld_path - application_path = data[14] - else: - application_path = "rigctld" - command.append(f'{application_path}') - elif sys.platform in ["win32", "win64"]: - isWin=True - if data[13].lower() == "localhost": - data[13]="127.0.0.1" - if data[14] not in [""]: - # hamlib_rigctld_path - application_path = data[14] - else: - application_path = "rigctld.exe" - command.append(f'{application_path}') - - - options = [] - - # hamlib_deviceid - if data[1] not in [None, "None", "ignore"]: - options.append(("--model=" + data[1] )) - - # hamlib_deviceport - if data[2] not in [None, "None", "ignore"]: - options.append(("--rig-file="+ data[2])) - # hamlib_stop_bits - if data[3] not in [None, "None", "ignore"]: - options.append(("--set-conf=stop_bits=" + data[3])) - - # hamlib_data_bits - if data[4] not in [None, "None", "ignore"]: - options.append(("--set-conf=data_bits=" + data[4])) - - - # hamlib_handshake - if data[5] not in [None, "None", "ignore"]: - options.append(("--set-conf=serial_handshake=" + data[5])) - - - # hamlib_serialspeed - if data[6] not in [None, "None", "ignore"]: - options.append(("--serial-speed=" + data[6])) - - # hamlib_dtrstate - if data[7] not in [None, "None", "ignore"]: - options.append(("--set-conf=dtr_state=" + data[7])) - - - # hamlib_pttprotocol - if data[8] not in [None, "None", "ignore"]: - options.append(("--ptt-type=" + data[8])) - - - # hamlib_ptt_port - if data[9] not in [None, "None", "ignore"]: - options.append(("--ptt-file=" + data[9])) - - - # hamlib_dcd - if data[10] not in [None, "None", "ignore"]: - options.append(("--dcd-type=" + data[10])) - - - # hamlbib_serialspeed_ptt - if data[11] not in [None, "None", "ignore"]: - # options.extend(("-m", data[11])) - pass - - # hamlib_rigctld_port - # Using this ensures rigctld starts on port configured in GUI - if data[12] not in [None, "None", "ignore"]: - options.append(("--port="+ data[12])) - - # hamlib_rigctld_ip - if data[13] not in [None, "None", "ignore"]: - options.append(("--listen-addr="+ data[13])) - - # data[14] == hamlib_rigctld_path - # maybe at wrong place in list... - #Not needed for setting command line arguments - - # hamlib_rigctld_server_port - # Ignore configured value and use value configured in GUI - #if data[15] not in [None, "None", "ignore"]: - # options.extend(("-m", data[15])) - # pass - - # hamlib_rigctld_custom_args - if data[16] not in [None, "None", "ignore"]: - for o in data[16].split(" "): - options.append(o) - - # append debugging paramter - # disabled as this could be set via gui - #options.append(("-vvv")) - command += options - - self.log.info("[DMN] starting rigctld: ", param=command) - - if not isWin: - # NOTE --> It seems Popen is non blocking, while run is blocking - #proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - proc = subprocess.Popen(command) - #proc = subprocess.run(command, shell=False, check=True, text=True, capture_output=True) - else: - #On windows, open rigctld in new window for easier troubleshooting - proc = subprocess.Popen(command, creationflags=subprocess.CREATE_NEW_CONSOLE,close_fds=True) - - Daemon.rigctldstarted = True - Daemon.rigctldprocess = proc - - atexit.register(proc.kill) - - except Exception as err: - self.log.warning("[DMN] err starting rigctld: ", e=err) - - def start_modem(self, data): - self.log.warning("[DMN] Starting Modem", rig=data[5], port=data[6]) - # list of parameters, necessary for running subprocess command as a list - options = ["--port", str(DAEMON.port - 1)] - - # create an additional list entry for parameters not covered by gui - data[50] = int(DAEMON.port - 1) - - options.append("--mycall") - options.extend((data[1], "--mygrid")) - options.extend((data[2], "--rx")) - options.extend((data[3], "--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[5] != "disabled": - - options.append("--radiocontrol") - options.append(data[5]) - - if data[5] == "rigctld": - options.append("--rigctld_ip") - options.extend((data[6], "--rigctld_port")) - options.append(data[7]) - - if data[5] == "tci": - options.append("--tci-ip") - options.extend((data[22], "--tci-port")) - options.append(data[23]) - - - if data[8] == "True": - options.append("--scatter") - - if data[9] == "True": - options.append("--fft") - - if data[10] == "True": - options.append("--500hz") - - options.append("--tuning_range_fmin") - options.extend((data[11], "--tuning_range_fmax")) - options.extend((data[12], "--tx-audio-level")) - options.append(data[14]) - - if data[15] == "True": - options.append("--qrv") - - options.append("--rx-buffer-size") - options.append(data[16]) - - if data[17] == "True": - options.append("--explorer") - - options.append("--ssid") - options.extend(str(i) for i in data[18]) - if data[19] == "True": - options.append("--tune") - - if data[20] == "True": - options.append("--stats") - - if data[13] == "True": - options.append("--fsk") - - options.append("--tx-delay") - options.append(data[21]) - - #Mesh - if data[24] == "True": - options.append("--mesh") - - - - options.append("--rx-audio-level") - options.append(data[25]) - - #Morse identifier - if data[26] == "True": - options.append("--morse") - - - # safe data to config file - config.write_entire_config(data) - - # Try running modem from binary, else run from source - # This helps running the modem in a developer environment - try: - command = [] - - if (getattr(sys, 'frozen', False) or hasattr(sys, "_MEIPASS")) and sys.platform in ["darwin"]: - # If the application is run as a bundle, the PyInstaller bootloader - # extends the sys module by a flag frozen=True and sets the app - # path into variable _MEIPASS'. - application_path = sys._MEIPASS - command.append(f'{application_path}/freedata-modem') - - elif sys.platform in ["linux", "darwin"]: - command.append("./freedata-modem") - elif sys.platform in ["win32", "win64"]: - command.append("freedata-modem.exe") - - command += options - - proc = subprocess.Popen(command) - - atexit.register(proc.kill) - - Daemon.modemprocess = proc - Daemon.modemstarted = True - - - self.log.info("[DMN] Modem started", path="binary") - except FileNotFoundError as err1: - - try: - self.log.info("[DMN] worker: ", e=err1) - command = [] - - if sys.platform in ["linux", "darwin"]: - command.append("python3") - elif sys.platform in ["win32", "win64"]: - command.append("python") - - command.append("deprecated_main.py") - command += options - proc = subprocess.Popen(command) - atexit.register(proc.kill) - - self.log.info("[DMN] Modem started", path="source") - - Daemon.modemprocess = proc - Daemon.modemstarted = True - except Exception as e: - self.log.error("[DMN] Modem not started", error=e) - Daemon.modemstarted = False - - - -if __name__ == "__main__": - mainlog = structlog.get_logger(__file__) - # we need to run this on Windows for multiprocessing support - multiprocessing.freeze_support() - - # --------------------------------------------GET PARAMETER INPUTS - PARSER = argparse.ArgumentParser(description="FreeDATA Daemon") - PARSER.add_argument( - "--port", - dest="socket_port", - default=3001, - help="Socket port in the range of 1024-65535", - type=int, - ) - ARGS = PARSER.parse_args() - - DAEMON.port = 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 err: - mainlog.error("[DMN] logger init error", exception=err) - - # init config - config = config.CONFIG("config.ini") - - try: - mainlog.info("[DMN] Starting TCP/IP socket", port=DAEMON.port) - # https://stackoverflow.com/a/16641793 - socketserver.TCPServer.allow_reuse_address = True - cmdserver = deprecated_sock.ThreadedTCPServer( - (Modem.host, DAEMON.port), sock.ThreadedTCPRequestHandler - ) - server_thread = threading.Thread(target=cmdserver.serve_forever) - server_thread.daemon = True - server_thread.start() - - except Exception as err: - mainlog.error( - "[DMN] Starting TCP/IP socket failed", port=DAEMON.port, e=err - ) - sys.exit(1) - daemon = DAEMON() - - mainlog.info( - "[DMN] Starting FreeDATA Daemon", - author="DJ2LS", - year="2023", - version=Modem.version, - ) - while True: - threading.Event().wait(1) diff --git a/modem/data cemetery/deprecated_data_handler.py b/modem/data cemetery/deprecated_data_handler.py deleted file mode 100644 index 15dd73f6..00000000 --- a/modem/data cemetery/deprecated_data_handler.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: UTF-8 -*- -""" -Created on Sun Dec 27 20:43:40 2020 - -@author: DJ2LS -""" -# pylint: disable=invalid-name, line-too-long, c-extension-no-member -# pylint: disable=import-outside-toplevel, attribute-defined-outside-init -# pylint: disable=fixme - - -import threading -import helpers -import structlog -from modem_frametypes import FRAME_TYPE as FR_TYPE -import event_manager - - - -TESTMODE = False - - -class DATA: - """Terminal Node Controller for FreeDATA""" - - log = structlog.get_logger("DATA") - - def __init__(self, config, event_queue, states): - - self.config = config - self.event_queue = event_queue - self.states = states - - - diff --git a/modem/data cemetery/deprecated_data_handler_broadcasts.py b/modem/data cemetery/deprecated_data_handler_broadcasts.py deleted file mode 100644 index d17f73e1..00000000 --- a/modem/data cemetery/deprecated_data_handler_broadcasts.py +++ /dev/null @@ -1,194 +0,0 @@ -import time -from modem_frametypes import FRAME_TYPE as FR_TYPE -from codec2 import FREEDV_MODE -from queues import MODEM_TRANSMIT_QUEUE -import helpers -from random import randrange -import uuid -import structlog -import event_manager -import command_qrv - -from deprecated_data_handler import DATA - -TESTMODE = False - -class BROADCAST(DATA): - - def __init__(self, config, event_queue, states): - super().__init__(config, event_queue, states) - - self.log = structlog.get_logger("DHBC") - self.states = states - self.event_queue = event_queue - self.config = config - - self.event_manager = event_manager.EventManager([event_queue]) - - # length of signalling frame - self.length_sig0_frame = 14 - self.modem_frequency_offset = 0 - - # load config - self.mycallsign = config['STATION']['mycall'] - self.myssid = config['STATION']['myssid'] - self.mycallsign += "-" + str(self.myssid) - encoded_call = helpers.callsign_to_bytes(self.mycallsign) - self.mycallsign_bytes = helpers.bytes_to_callsign(encoded_call) - self.mygrid = config['STATION']['mygrid'] - self.enable_fsk = config['MODEM']['enable_fsk'] - self.respond_to_cq = config['MODEM']['respond_to_cq'] - self.respond_to_call = True - - self.duration_datac13 = 2.0 - self.duration_sig1_frame = self.duration_datac13 - - def received_cq(self, frame_data, snr) -> None: - """ - Called when we receive a CQ frame - Args: - data_in:bytes: - - Returns: - Nothing - """ - # here we add the received station to the heard stations buffer - dxcallsign = frame_data['origin'] - self.log.debug("[Modem] received_cq:", dxcallsign=dxcallsign) - self.dxgrid = frame_data['gridsquare'] - - self.event_manager.send_custom_event( - freedata="modem-message", - cq="received", - mycallsign=self.mycallsign, - dxcallsign=dxcallsign, - dxgrid=self.dxgrid, - ) - - self.log.info( - "[Modem] CQ RCVD [" - + dxcallsign - + "][" - + self.dxgrid - + "] ", - snr=snr, - ) - helpers.add_to_heard_stations( - dxcallsign, - self.dxgrid, - "CQ CQ CQ", - snr, - self.modem_frequency_offset, - self.states.radio_frequency, - self.states.heard_stations - ) - - # Sleep a random amount of time before responding to make it more likely to be - # heard when many stations respond. Each DATAC0 frame is 0.44 sec (440ms) in - # duration, plus overhead. Set the wait interval to be random between 0 and - # self.duration_sig1_frame * 4 == 4 slots - # in self.duration_sig1_frame increments. - if self.respond_to_cq and self.respond_to_call: - params = {'snr': snr, 'dxcall': dxcallsign} - cmd = command_qrv.QRVCommand(self.config, self.log, params) - cmd.run(self.event_queue, MODEM_TRANSMIT_QUEUE) - - def received_qrv(self, data_in: bytes, snr) -> None: - """ - Called when we receive a QRV frame - Args: - data_in:bytes: - - """ - # here we add the received station to the heard stations buffer - dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7])) - self.dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8") - dxsnr = helpers.snr_from_bytes(data_in[11:12]) - - combined_snr = f"{snr}/{dxsnr}" - - self.event_manager.send_custom_event( - freedata="modem-message", - qrv="received", - dxcallsign=str(dxcallsign, "UTF-8"), - dxgrid=str(self.dxgrid, "UTF-8"), - snr=str(snr), - dxsnr=str(dxsnr) - ) - - self.log.info( - "[Modem] QRV RCVD [" - + str(dxcallsign, "UTF-8") - + "][" - + str(self.dxgrid, "UTF-8") - + "] ", - snr=snr, - dxsnr=dxsnr - ) - helpers.add_to_heard_stations( - dxcallsign, - self.dxgrid, - "QRV", - combined_snr, - self.modem_frequency_offset, - self.states.radio_frequency, - self.states.heard_stations - ) - - def received_is_writing(self, data_in: bytes, snr) -> None: - """ - Called when we receive a IS WRITING frame - Args: - data_in:bytes: - - """ - # here we add the received station to the heard stations buffer - dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7])) - - self.event_manager.send_custom_event( - freedata="modem-message", - fec="is_writing", - dxcallsign=str(dxcallsign, "UTF-8") - ) - - self.log.info( - "[Modem] IS_WRITING RCVD [" - + str(dxcallsign, "UTF-8") - + "] ", - ) - - # ----------- BROADCASTS - def received_beacon(self, frame_data, snr) -> None: - """ - Called if we received a beacon - Args: - data_in:bytes: - - """ - # here we add the received station to the heard stations buffer - beacon_callsign = frame_data['origin'] - self.dxgrid = frame_data['gridsquare'] - - self.event_manager.send_custom_event( - freedata="modem-message", - beacon="received", - uuid=str(uuid.uuid4()), - timestamp=int(time.time()), - dxcallsign=beacon_callsign, - dxgrid=self.dxgrid, - snr=str(snr), - ) - - self.log.info( - f"[Modem] BEACON RCVD [{beacon_callsign}][{self.dxgrid}]", - snr=snr, - ) - helpers.add_to_heard_stations( - beacon_callsign, - self.dxgrid, - "BEACON", - snr, - self.modem_frequency_offset, - self.states.radio_frequency, - self.states.heard_stations - ) diff --git a/modem/data cemetery/deprecated_data_handler_data_broadcasts.py b/modem/data cemetery/deprecated_data_handler_data_broadcasts.py deleted file mode 100644 index dc00c260..00000000 --- a/modem/data cemetery/deprecated_data_handler_data_broadcasts.py +++ /dev/null @@ -1,122 +0,0 @@ -import structlog -import threading -import helpers -import time -import modem -import base64 -import ujson as json -from deprecated_data_handler import DATA -class DATABROADCAST(DATA): - """Terminal Node Controller for FreeDATA""" - - log = structlog.get_logger("BROADCAST") - - def __init__(self, config, event_queue, states) -> None: - super().__init__(config, event_queue, states) - - self.log = structlog.get_logger("DHDBC") - self.states = states - self.event_queue = event_queue - - self.mycallsign = config['STATION']['mycall'] - - self.fec_wakeup_callsign = bytes() - self.longest_duration = 6 - self.wakeup_received = False - self.broadcast_timeout_reached = False - self.broadcast_payload_bursts = 1 - self.broadcast_watchdog = threading.Thread( - target=self.watchdog, name="watchdog thread", daemon=True - ) - self.broadcast_watchdog.start() - self.event_queue = event_queue - - def received_fec_wakeup(self, data_in: bytes): - self.fec_wakeup_callsign = helpers.bytes_to_callsign(bytes(data_in[1:7])) - self.wakeup_mode = int.from_bytes(bytes(data_in[7:8]), "big") - bursts = int.from_bytes(bytes(data_in[8:9]), "big") - self.wakeup_received = True - - modem.RECEIVE_DATAC4 = True - - self.send_data_to_socket_queue( - freedata="modem-message", - fec="wakeup", - mode=self.wakeup_mode, - bursts=bursts, - dxcallsign=str(self.fec_wakeup_callsign, "UTF-8") - ) - - self.log.info( - "[Modem] FRAME WAKEUP RCVD [" - + str(self.fec_wakeup_callsign, "UTF-8") - + "] ", mode=self.wakeup_mode, bursts=bursts, - ) - - def received_fec(self, data_in: bytes): - print(self.fec_wakeup_callsign) - - self.send_data_to_socket_queue( - freedata="modem-message", - fec="broadcast", - dxcallsign=str(self.fec_wakeup_callsign, "UTF-8"), - data=base64.b64encode(data_in[1:]).decode("UTF-8") - ) - - self.log.info("[Modem] FEC DATA RCVD") - - def send_data_to_socket_queue(self, **jsondata): - """ - Send information to the UI via JSON and the sock.SOCKET_QUEUE. - - Args: - Dictionary containing the data to be sent, in the format: - key=value, for each item. E.g.: - self.send_data_to_socket_queue( - freedata="modem-message", - arq="received", - status="success", - uuid=self.transmission_uuid, - timestamp=timestamp, - mycallsign=str(self.mycallsign, "UTF-8"), - dxcallsign=str(Station.dxcallsign, "UTF-8"), - dxgrid=str(Station.dxgrid, "UTF-8"), - data=base64_data, - ) - """ - - # add mycallsign and dxcallsign to network message if they not exist - # and make sure we are not overwrite them if they exist - try: - if "mycallsign" not in jsondata: - jsondata["mycallsign"] = str(self.mycallsign, "UTF-8") - except Exception as e: - self.log.debug("[Modem] error adding callsigns to network message", e=e) - - # run json dumps - json_data_out = json.dumps(jsondata) - - self.log.debug("[Modem] send_data_to_socket_queue:", jsondata=json_data_out) - # finally push data to our network queue - self.event_queue.put(json_data_out) - - def watchdog(self): - while 1: - if self.wakeup_received: - timeout = time.time() + (self.longest_duration * self.broadcast_payload_bursts) + 2 - while time.time() < timeout: - threading.Event().wait(0.01) - - self.broadcast_timeout_reached = True - - self.log.info( - "[Modem] closing broadcast slot [" - + str(self.fec_wakeup_callsign, "UTF-8") - + "] ", mode=self.wakeup_mode, bursts=self.broadcast_payload_bursts, - ) - # TODO We need a dynamic way of modifying this - modem.RECEIVE_DATAC4 = False - self.fec_wakeup_callsign = bytes() - self.wakeup_received = False - else: - threading.Event().wait(0.01) diff --git a/modem/data cemetery/deprecated_data_handler_ping.py b/modem/data cemetery/deprecated_data_handler_ping.py deleted file mode 100644 index af908b91..00000000 --- a/modem/data cemetery/deprecated_data_handler_ping.py +++ /dev/null @@ -1,145 +0,0 @@ -import time -from modem_frametypes import FRAME_TYPE as FR_TYPE -from codec2 import FREEDV_MODE -import helpers -import uuid -import structlog -from deprecated_data_handler import DATA -class PING(DATA): - def __init__(self, config, event_queue, states): - super().__init__(config, event_queue, states) - - self.log = structlog.get_logger("DHPING") - self.states = states - self.event_queue = event_queue - self.config = config - - def received_ping(self, deconstructed_frame: list, snr) -> None: - """ - Called if we received a ping - - Args: - data_in:bytes: - - """ - destination_crc = deconstructed_frame["destination_crc"] - origin_crc = deconstructed_frame["origin_crc"] - origin = deconstructed_frame["origin"] - - # check if callsign ssid override - valid, mycallsign = helpers.check_callsign(self.config['STATION']['mycall'], destination_crc, self.config['STATION']['ssid_list']) - if not valid: - # PING packet not for me. - self.log.debug("[Modem] received_ping: ping not for this station.") - return - - self.dxcallsign_crc = origin_crc - self.dxcallsign = origin - self.log.info( - f"[Modem] PING REQ from [{origin}] to [{mycallsign}]", - snr=snr, - ) - - self.dxgrid = "" - helpers.add_to_heard_stations( - origin, - self.dxgrid, - "PING", - snr, - -999, # TODO we don't have the offset available here yet... - self.states.radio_frequency, - self.states.heard_stations - ) - - self.event_queue.put({ - "freedata": "modem-message", - "ping": "received", - "uuid": str(uuid.uuid4()), - "timestamp": int(time.time()), - "dxgrid": self.dxgrid, - "dxcallsign": origin, - "mycallsign": mycallsign, - "snr": str(snr), - }) - - self.transmit_ping_ack(snr) - - def transmit_ping_ack(self, snr): - """ - - transmit a ping ack frame - called by def received_ping - """ - - self.log.warning('DATA_HANDLER_PING: REVISE PING ACK!') - return - - ping_frame = bytearray(self.length_sig0_frame) - ping_frame[:1] = bytes([FR_TYPE.PING_ACK.value]) - ping_frame[1:4] = self.dxcallsign_crc - ping_frame[4:7] = self.mycallsign_crc - ping_frame[7:11] = helpers.encode_grid(self.mygrid) - ping_frame[13:14] = helpers.snr_to_bytes(snr) - - if self.enable_fsk: - self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value) - else: - self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.sig0.value) - - def received_ping_ack(self, data_in: bytes, snr) -> None: - """ - Called if a PING ack has been received - Args: - data_in:bytes: - - """ - - # check if we received correct ping - # check if callsign ssid override - _valid, mycallsign = helpers.check_callsign(self.mycallsign, data_in[1:4], self.ssid_list) - if _valid: - - self.dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8") - dxsnr = helpers.snr_from_bytes(data_in[13:14]) - self.send_data_to_socket_queue( - freedata="modem-message", - ping="acknowledge", - uuid=str(uuid.uuid4()), - timestamp=int(time.time()), - dxgrid=str(self.dxgrid, "UTF-8"), - dxcallsign=str(self.dxcallsign, "UTF-8"), - mycallsign=str(mycallsign, "UTF-8"), - snr=str(snr), - dxsnr=str(dxsnr) - ) - # combined_snr = own rx snr / snr on dx side - combined_snr = f"{snr}/{dxsnr}" - helpers.add_to_heard_stations( - self.dxcallsign, - self.dxgrid, - "PING-ACK", - combined_snr, - self.modem_frequency_offset, - self.states.radio_frequency, - self.states.heard_stations - ) - - self.log.info( - "[Modem] PING ACK [" - + str(mycallsign, "UTF-8") - + "] >|< [" - + str(self.dxcallsign, "UTF-8") - + "]", - snr=snr, - dxsnr=dxsnr, - ) - self.states.set("is_modem_busy", False) - else: - self.log.info( - "[Modem] FOREIGN PING ACK [" - + str(self.mycallsign, "UTF-8") - + "] ??? [" - + str(bytes(data_in[4:7]), "UTF-8") - + "]", - snr=snr, - ) diff --git a/modem/data cemetery/deprecated_main.py b/modem/data cemetery/deprecated_main.py deleted file mode 100755 index dc22de71..00000000 --- a/modem/data cemetery/deprecated_main.py +++ /dev/null @@ -1,253 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- -""" -Created on Tue Dec 22 16:58:45 2020 - -@author: DJ2LS - -main module for running the modem -""" - - -# run modem self test on startup before we are doing other things -# import selftest -# selftest.TEST() - -# continue if we passed the test - -import multiprocessing -import os -import signal -import socketserver -import sys -import threading -import argparse -import config -import data_handler -import helpers -import log_handler -import modem -from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem, MeshParam -import structlog -import explorer -import json -import mesh -from os.path import exists - -log = structlog.get_logger("main") - -def signal_handler(sig, frame): - """ - a signal handler, which closes the network/socket when closing the application - Args: - sig: signal - frame: - - Returns: system exit - - """ - print("Closing Modem...") - deprecated_sock.CLOSE_SIGNAL = True - sys.exit(0) - - -signal.signal(signal.SIGINT, signal_handler) - -# This is for Windows multiprocessing support -multiprocessing.freeze_support() - -parser = argparse.ArgumentParser() -parser.add_argument("--use-config", - help = "Specify a config file", - default = 'config.ini') -args = parser.parse_args() - -# init config -config_file = args.use_config -if not exists(config_file): - print("Config file %s not found. Exiting." % config_file) - exit(1) - -conf = config.CONFIG(config_file) - -try: - # 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 - mycallsign = bytes(conf.get('STATION', 'mycall', 'AA0AA'), "utf-8") - mycallsign = helpers.callsign_to_bytes(mycallsign) - Station.mycallsign = helpers.bytes_to_callsign(mycallsign) - Station.mycallsign_crc = helpers.get_crc_24(Station.mycallsign) - - #json.loads = for converting str list to list - Station.ssid_list = json.loads(conf.get('STATION', 'ssid_list', '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]')) - - # init config - conf = config.CONFIG(config_file) - try: - # 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 - mycallsign = bytes(conf.get('STATION', 'mycall', 'AA0AA'), "utf-8") - mycallsign = helpers.callsign_to_bytes(mycallsign) - Station.mycallsign = helpers.bytes_to_callsign(mycallsign) - Station.mycallsign_crc = helpers.get_crc_24(Station.mycallsign) - - #json.loads = for converting str list to list - Station.ssid_list = json.loads(conf.get('STATION', 'ssid_list', '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]')) - - Station.mygrid = bytes(conf.get('STATION', 'mygrid', 'JN12aa'), "utf-8") - # check if we have an int or str as device name - try: - AudioParam.audio_input_device = int(conf.get('AUDIO', 'rx', '0')) - except ValueError: - AudioParam.audio_input_device = conf.get('AUDIO', 'rx', '0') - try: - AudioParam.audio_output_device = int(conf.get('AUDIO', 'tx', '0')) - except ValueError: - AudioParam.audio_output_device = conf.get('AUDIO', 'tx', '0') - - Modem.port = int(conf.get('NETWORK', 'modemport', '3000')) - HamlibParam.hamlib_radiocontrol = conf.get('RADIO', 'radiocontrol', 'disabled') - HamlibParam.hamlib_rigctld_ip = conf.get('RADIO', 'rigctld_ip', '127.0.0.1') - HamlibParam.hamlib_rigctld_port = str(conf.get('RADIO', 'rigctld_port', '4532')) - ModemParam.enable_scatter = conf.get('Modem', 'scatter', 'True') - AudioParam.enable_fft = conf.get('Modem', 'fft', 'True') - Modem.enable_fsk = conf.get('Modem', 'fsk', 'False') - Modem.low_bandwidth_mode = conf.get('Modem', 'narrowband', 'False') - ModemParam.tuning_range_fmin = float(conf.get('Modem', 'fmin', '-50.0')) - ModemParam.tuning_range_fmax = float(conf.get('Modem', 'fmax', '50.0')) - AudioParam.tx_audio_level = int(conf.get('AUDIO', 'txaudiolevel', '0')) - AudioParam.rx_audio_level = int(conf.get('AUDIO', 'rxaudiolevel', '0')) - Modem.respond_to_cq = conf.get('Modem', 'qrv', 'True') - ARQ.rx_buffer_size = int(conf.get('Modem', 'rx_buffer_size', '16')) - Modem.enable_explorer = conf.get('Modem', 'explorer', 'False') - AudioParam.audio_auto_tune = conf.get('AUDIO', 'auto_tune', 'False') - Modem.enable_stats = conf.get('Modem', 'stats', 'False') - TCIParam.ip = str(conf.get('TCI', 'tci_ip', 'localhost')) - TCIParam.port = int(conf.get('TCI', 'tci_port', '50001')) - ModemParam.tx_delay = int(conf.get('Modem', 'tx_delay', '0')) - MeshParam.enable_protocol = conf.get('MESH','mesh_enable','False') - MeshParam.transmit_morse_identifier = conf.get('Modem','transmit_morse_identifier','False') - - except KeyError as e: - log.warning("[CFG] Error reading config file near", key=str(e)) - except Exception as e: - log.warning("[CFG] Error", e=e) - - # make sure the own ssid is always part of the ssid list - my_ssid = int(Station.mycallsign.split(b'-')[1]) - if my_ssid not in Station.ssid_list: - Station.ssid_list.append(my_ssid) - - Station.mygrid = bytes(conf.get('STATION', 'mygrid', 'JN12aa'), "utf-8") - # check if we have an int or str as device name - - # we need to wait until we got all parameters from argparse first before we can load the other modules - import deprecated_sock - - try: - AudioParam.audio_input_device = int(conf.get('AUDIO', 'rx', '0')) - except ValueError: - AudioParam.audio_input_device = conf.get('AUDIO', 'rx', '0') - try: - AudioParam.audio_output_device = int(conf.get('AUDIO', 'tx', '0')) - except ValueError: - AudioParam.audio_output_device = conf.get('AUDIO', 'tx', '0') - - Modem.port = int(conf.get('NETWORK', 'modemport', '3000')) - HamlibParam.hamlib_radiocontrol = conf.get('RADIO', 'radiocontrol', 'disabled') - HamlibParam.hamlib_rigctld_ip = conf.get('RADIO', 'rigctld_ip', '127.0.0.1') - HamlibParam.hamlib_rigctld_port = str(conf.get('RADIO', 'rigctld_port', '4532')) - ModemParam.enable_scatter = conf.get('Modem', 'scatter', 'True') - AudioParam.enable_fft = conf.get('Modem', 'fft', 'True') - Modem.enable_fsk = conf.get('Modem', 'fsk', 'False') - Modem.low_bandwidth_mode = conf.get('Modem', 'narrowband', 'False') - ModemParam.tuning_range_fmin = float(conf.get('Modem', 'fmin', '-50.0')) - ModemParam.tuning_range_fmax = float(conf.get('Modem', 'fmax', '50.0')) - AudioParam.tx_audio_level = int(conf.get('AUDIO', 'txaudiolevel', '100')) - Modem.respond_to_cq = conf.get('Modem', 'qrv', 'True') - ARQ.rx_buffer_size = int(conf.get('Modem', 'rx_buffer_size', '16')) - ARQ.arq_save_to_folder = conf.get('Modem', 'save_to_folder', 'False') - Modem.enable_explorer = conf.get('Modem', 'explorer', 'False') - AudioParam.audio_auto_tune = conf.get('AUDIO', 'auto_tune', 'False') - Modem.enable_stats = conf.get('Modem', 'stats', 'False') - TCIParam.ip = str(conf.get('TCI', 'tci_ip', 'localhost')) - TCIParam.port = int(conf.get('TCI', 'tci_port', '50001')) - ModemParam.tx_delay = int(conf.get('Modem', 'tx_delay', '0')) - MeshParam.enable_protocol = conf.get('MESH','mesh_enable','False') -except KeyError as e: - log.warning("[CFG] Error reading config file near", key=str(e)) -except Exception as e: - log.warning("[CFG] Error", e=e) - -# make sure the own ssid is always part of the ssid list -my_ssid = int(Station.mycallsign.split(b'-')[1]) -if my_ssid not in Station.ssid_list: - Station.ssid_list.append(my_ssid) - -# we need to wait until we got all parameters from argparse first before we can load the other modules -import deprecated_sock - -# config logging -try: - if sys.platform == "linux": - logging_path = os.getenv("HOME") + "/.config/" + "FreeDATA/" + "modem" - - if sys.platform == "darwin": - logging_path = ( - os.getenv("HOME") - + "/Library/" - + "Application Support/" - + "FreeDATA/" - + "modem" - ) - - if sys.platform in ["win32", "win64"]: - logging_path = os.getenv("APPDATA") + "/" + "FreeDATA/" + "modem" - - if not os.path.exists(logging_path): - os.makedirs(logging_path) - log_handler.setup_logging(logging_path) -except Exception as err: - log.error("[DMN] logger init error", exception=err) - -log.info( - "[Modem] Starting FreeDATA", author="DJ2LS", version=Modem.version -) - -# start data handler -data_handler.DATA(conf.config) - -# start modem -modem = modem.RF(conf.config) - -# start mesh protocol only if enabled -if MeshParam.enable_protocol: - log.info("[MESH] loading module") - # start mesh module - mesh = mesh.MeshRouter() - -# optionally start explorer module -if Modem.enable_explorer: - log.info("[EXPLORER] Publishing to https://explorer.freedata.app", state=Modem.enable_explorer) - explorer = explorer.explorer() - -# --------------------------------------------START CMD SERVER -try: - log.info("[Modem] Starting TCP/IP socket", port=Modem.port) - # https://stackoverflow.com/a/16641793 - socketserver.TCPServer.allow_reuse_address = True - cmdserver = sock.ThreadedTCPServer( - (Modem.host, Modem.port), sock.ThreadedTCPRequestHandler - ) - server_thread = threading.Thread(target=cmdserver.serve_forever) - - server_thread.daemon = True - server_thread.start() - -except Exception as err: - log.error("[Modem] Starting TCP/IP socket failed", port=Modem.port, e=err) - sys.exit(1) - -server_thread.join() diff --git a/modem/data cemetery/deprecated_protocol_arq_connection.py b/modem/data cemetery/deprecated_protocol_arq_connection.py deleted file mode 100644 index 3a2a3489..00000000 --- a/modem/data cemetery/deprecated_protocol_arq_connection.py +++ /dev/null @@ -1,309 +0,0 @@ - -import time -import helpers -from codec2 import FREEDV_MODE -from modem_frametypes import FRAME_TYPE as FR_TYPE - -from deprecated_protocol_arq_session import ARQ - - -class SESSION(ARQ): - - def __init__(self, config, event_queue, states): - super().__init__(config, event_queue, states) - - def received_session_close(self, data_in: bytes, snr): - """ - Closes the session when a close session frame is received and - the DXCALLSIGN_CRC matches the remote station participating in the session. - - Args: - data_in:bytes: - - """ - # We've arrived here from process_data which already checked that the frame - # is intended for this station. - # Close the session if the CRC matches the remote station in static. - _valid_crc, mycallsign = helpers.check_callsign(self.mycallsign, bytes(data_in[2:5]), self.ssid_list) - _valid_session = helpers.check_session_id(self.session_id, bytes(data_in[1:2])) - if (_valid_crc or _valid_session) and self.states.arq_session_state not in ["disconnected"]: - self.states.set("arq_session_state", "disconnected") - self.dxgrid = b'------' - helpers.add_to_heard_stations( - self.dxcallsign, - self.dxgrid, - "DATA", - snr, - self.modem_frequency_offset, - self.states.radio_frequency, - self.states.heard_stations - ) - self.log.info( - "[Modem] SESSION [" - + str(self.mycallsign, "UTF-8") - + "]<>[" - + str(self.dxcallsign, "UTF-8") - + "]", - self.states.arq_session_state, - ) - - self.event_manager.send_custom_event( - freedata="modem-message", - arq="session", - status="close", - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - ) - - self.IS_ARQ_SESSION_MASTER = False - self.states.is_arq_session = False - self.arq_cleanup() - - def transmit_session_heartbeat(self) -> None: - """Send ARQ sesion heartbeat while connected""" - # self.states.is_arq_session = True - # self.states.set("is_modem_busy", True) - # self.states.set("arq_session_state", "connected") - - connection_frame = bytearray(self.length_sig0_frame) - connection_frame[:1] = bytes([FR_TYPE.ARQ_SESSION_HB.value]) - connection_frame[1:2] = self.session_id - - self.event_manager.send_custom_event( - freedata="modem-message", - arq="session", - status="connected", - heartbeat="transmitting", - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - ) - - self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) - - def received_session_heartbeat(self, data_in: bytes, snr) -> None: - """ - Received an ARQ session heartbeat, record and update state accordingly. - Args: - data_in:bytes: - - """ - # Accept session data if the DXCALLSIGN_CRC matches the station in static or session id. - _valid_crc, _ = helpers.check_callsign(self.dxcallsign, bytes(data_in[4:7]), self.ssid_list) - _valid_session = helpers.check_session_id(self.session_id, bytes(data_in[1:2])) - if _valid_crc or _valid_session and self.states.arq_session_state in ["connected", "connecting"]: - self.log.debug("[Modem] Received session heartbeat") - self.dxgrid = b'------' - helpers.add_to_heard_stations( - self.dxcallsign, - self.dxgrid, - "SESSION-HB", - snr, - self.modem_frequency_offset, - self.states.radio_frequency, - self.states.heard_stations - ) - - self.event_manager.send_custom_event( - freedata="modem-message", - arq="session", - status="connected", - heartbeat="received", - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - ) - - self.states.is_arq_session = True - self.states.set("arq_session_state", "connected") - self.states.set("is_modem_busy", True) - - # Update the timeout timestamps - self.arq_session_last_received = int(time.time()) - self.data_channel_last_received = int(time.time()) - # transmit session heartbeat only - # -> if not session master - # --> this will be triggered by heartbeat watchdog - # -> if not during a file transfer - # -> if ARQ_SESSION_STATE != disconnecting, disconnected, failed - # --> to avoid heartbeat toggle loops while disconnecting - if ( - not self.IS_ARQ_SESSION_MASTER - and not self.arq_file_transfer - and self.states.arq_session_state != 'disconnecting' - and self.states.arq_session_state != 'disconnected' - and self.states.arq_session_state != 'failed' - ): - self.transmit_session_heartbeat() - - - def close_session(self) -> None: - """Close the ARQ session""" - self.states.set("arq_session_state", "disconnecting") - - self.log.info( - "[Modem] SESSION [" - + str(self.mycallsign, "UTF-8") - + "]<>[" - + str(self.dxcallsign, "UTF-8") - + "]", - self.states.arq_session_state, - ) - - self.event_manager.send_custom_event( - freedata="modem-message", - arq="session", - status="close", - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - ) - - self.IS_ARQ_SESSION_MASTER = False - self.states.is_arq_session = False - - # we need to send disconnect frame before doing arq cleanup - # we would lose our session id then - self.send_disconnect_frame() - - # transmit morse identifier if configured - if self.enable_morse_identifier: - MODEM_TRANSMIT_QUEUE.put(["morse", 1, 0, self.mycallsign]) - self.arq_cleanup() - - def open_session(self) -> bool: - """ - Create and send the frame to request a connection. - - Returns: - True if the session was opened successfully - False if the session open request failed - """ - self.IS_ARQ_SESSION_MASTER = True - self.states.set("arq_session_state", "connecting") - - # create a random session id - self.session_id = np.random.bytes(1) - - # build connection frame - connection_frame = self.frame_factory.build_arq_session_connect( - session_id=self.session_id, - destination_crc=self.dxcallsign_crc, - ) - while not self.states.is_arq_session: - threading.Event().wait(0.01) - for attempt in range(self.session_connect_max_retries): - self.log.info( - "[Modem] SESSION [" - + str(self.mycallsign, "UTF-8") - + "]>>?<<[" - + str(self.dxcallsign, "UTF-8") - + "]", - a=f"{str(attempt + 1)}/{str(self.session_connect_max_retries)}", - state=self.states.arq_session_state, - ) - - self.event_manager.send_custom_event( - freedata="modem-message", - arq="session", - status="connecting", - attempt=attempt + 1, - maxattempts=self.session_connect_max_retries, - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - ) - - self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) - - # Wait for a time, looking to see if `self.states.is_arq_session` - # indicates we've received a positive response from the far station. - timeout = time.time() + 3 - while time.time() < timeout: - threading.Event().wait(0.01) - # Stop waiting if data channel is opened - if self.states.is_arq_session: - return True - - # Stop waiting and interrupt if data channel is getting closed while opening - if self.states.arq_session_state == "disconnecting": - # disabled this session close as its called twice - # self.close_session() - return False - - # Session connect timeout, send close_session frame to - # attempt to clean up the far-side, if it received the - # open_session frame and can still hear us. - if not self.states.is_arq_session: - self.close_session() - return False - - # Given the while condition, it will only exit when `self.states.is_arq_session` is True - self.event_manager.send_custom_event( - freedata="modem-message", - arq="session", - status="connected", - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - ) - return True - - def received_session_opener(self, data_in: bytes, snr) -> None: - """ - Received a session open request packet. - - Args: - data_in:bytes: - """ - # if we don't want to respond to calls, return False - if not self.respond_to_call: - return False - - # ignore channel opener if already in ARQ STATE - # use case: Station A is connecting to Station B while - # Station B already tries connecting to Station A. - # For avoiding ignoring repeated connect request in case of packet loss - # we are only ignoring packets in case we are ISS - if self.states.is_arq_session and self.IS_ARQ_SESSION_MASTER: - return False - - self.IS_ARQ_SESSION_MASTER = False - self.states.set("arq_session_state", "connecting") - - # Update arq_session timestamp - self.arq_session_last_received = int(time.time()) - - self.session_id = bytes(data_in[1:2]) - self.dxcallsign_crc = bytes(data_in[5:8]) - self.dxcallsign = helpers.bytes_to_callsign(bytes(data_in[8:14])) - self.states.set("dxcallsign", self.dxcallsign) - - # check if callsign ssid override - valid, mycallsign = helpers.check_callsign(self.mycallsign, data_in[2:5], self.ssid_list) - self.mycallsign = mycallsign - self.dxgrid = b'------' - helpers.add_to_heard_stations( - self.dxcallsign, - self.dxgrid, - "DATA", - snr, - self.modem_frequency_offset, - self.states.radio_frequency, - self.states.heard_stations - ) - self.log.info( - "[Modem] SESSION [" - + str(self.mycallsign, "UTF-8") - + "]>>|<<[" - + str(self.dxcallsign, "UTF-8") - + "]", - self.states.arq_session_state, - ) - self.states.is_arq_session = True - self.states.set("is_modem_busy", True) - - self.event_manager.send_custom_event( - freedata="modem-message", - arq="session", - status="connected", - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - ) - - self.transmit_session_heartbeat() diff --git a/modem/data cemetery/deprecated_protocol_arq_session.py b/modem/data cemetery/deprecated_protocol_arq_session.py deleted file mode 100644 index e0dcc916..00000000 --- a/modem/data cemetery/deprecated_protocol_arq_session.py +++ /dev/null @@ -1,736 +0,0 @@ - -import threading -import time -import codec2 -import helpers -import modem -import stats -import structlog -from data_frame_factory import DataFrameFactory -from codec2 import FREEDV_MODE, FREEDV_MODE_USED_SLOTS -from modem_frametypes import FRAME_TYPE as FR_TYPE -import event_manager - -TESTMODE = False -class ARQ: - def __init__(self, config, event_queue, states): - - self.log = structlog.get_logger("DHARQ") - self.event_queue = event_queue - self.states = states - self.event_manager = event_manager.EventManager([event_queue]) - - self.frame_factory = DataFrameFactory(config) - - # ARQ PROTOCOL VERSION - # v.5 - signalling frame uses datac0 - # v.6 - signalling frame uses datac13 - # v.7 - adjusting ARQ timeout - # v.8 - adjusting ARQ structure - self.arq_protocol_version = 8 - - self.stats = stats.stats(config, event_queue, states) - - # load config - self.mycallsign = config['STATION']['mycall'] - self.myssid = config['STATION']['myssid'] - self.mycallsign += "-" + str(self.myssid) - encoded_call = helpers.callsign_to_bytes(self.mycallsign) - self.mycallsign = helpers.bytes_to_callsign(encoded_call) - self.ssid_list = config['STATION']['ssid_list'] - self.mycallsign_crc = helpers.get_crc_24(self.mycallsign) - self.mygrid = config['STATION']['mygrid'] - self.enable_fsk = config['MODEM']['enable_fsk'] - self.respond_to_cq = config['MODEM']['respond_to_cq'] - self.enable_hmac = config['MODEM']['enable_hmac'] - self.enable_stats = config['STATION']['enable_stats'] - self.enable_morse_identifier = config['MODEM']['enable_morse_identifier'] - self.arq_rx_buffer_size = config['MODEM']['rx_buffer_size'] - self.enable_experimental_features = False - # flag to indicate if modem running in low bandwidth mode - self.low_bandwidth_mode = config["MODEM"]["enable_low_bandwidth_mode"] - - # Enable general responding to channel openers for example - # this can be combined with a callsign blacklist for example - self.respond_to_call = True - - - self.modem_frequency_offset = 0 - - self.dxcallsign = b"ZZ9YY-0" - self.dxcallsign_crc = b'' - self.dxgrid = b'' - - # length of signalling frame - self.length_sig0_frame = 14 - self.length_sig1_frame = 14 - - # duration of frames - self.duration_datac4 = 5.17 - self.duration_datac13 = 2.0 - self.duration_datac1 = 4.18 - self.duration_datac3 = 3.19 - self.duration_sig0_frame = self.duration_datac13 - self.duration_sig1_frame = self.duration_datac13 - self.longest_duration = self.duration_datac4 - - # hold session id - self.session_id = bytes(1) - - # ------- ARQ SESSION - self.arq_file_transfer = False - self.IS_ARQ_SESSION_MASTER = False - self.arq_session_last_received = 0 - self.arq_session_timeout = 30 - self.session_connect_max_retries = 10 - - self.arq_compression_factor = 0 - - self.transmission_uuid = "" - - self.burst_last_received = 0.0 # time of last "live sign" of a burst - self.data_channel_last_received = 0.0 # time of last "live sign" of a frame - - # Flag to indicate if we received an ACK frame for a burst - self.burst_ack = False - # Flag to indicate if we received an ACK frame for a data frame - self.data_frame_ack_received = False - # Flag to indicate if we received a request for repeater frames - self.rpt_request_received = False - self.rpt_request_buffer = [] # requested frames, saved in a list - self.burst_rpt_counter = 0 - - - # 3 bytes for the BOF Beginning of File indicator in a data frame - self.data_frame_bof = b"BOF" - # 3 bytes for the EOF End of File indicator in a data frame - self.data_frame_eof = b"EOF" - - - - - - self.n_retries_per_burst = 0 - self.max_n_frames_per_burst = 1 - - # Flag to indicate if we received a low bandwidth mode channel opener - self.received_LOW_BANDWIDTH_MODE = False - - - self.data_channel_max_retries = 15 - - # event for checking arq_state_event - self.arq_state_event = threading.Event() - # -------------- AVAILABLE MODES START----------- - # IMPORTANT: LISTS MUST BE OF EQUAL LENGTH - - # --------------------- LOW BANDWIDTH - - # List of codec2 modes to use in "low bandwidth" mode. - self.mode_list_low_bw = [ - FREEDV_MODE.datac4.value, - ] - # List for minimum SNR operating level for the corresponding mode in self.mode_list - self.snr_list_low_bw = [-100] - # List for time to wait for corresponding mode in seconds - self.time_list_low_bw = [self.duration_datac4] - - # --------------------- HIGH BANDWIDTH - - # List of codec2 modes to use in "high bandwidth" mode. - self.mode_list_high_bw = [ - FREEDV_MODE.datac4.value, - FREEDV_MODE.datac3.value, - FREEDV_MODE.datac1.value, - ] - # List for minimum SNR operating level for the corresponding mode in self.mode_list - self.snr_list_high_bw = [-100, 0, 3] - # List for time to wait for corresponding mode in seconds - # test with 6,7 --> caused sometimes a frame timeout if ack frame takes longer - # TODO Need to check why ACK frames needs more time - # TODO Adjust these times - self.time_list_high_bw = [self.duration_datac4, self.duration_datac3, self.duration_datac1] - # -------------- AVAILABLE MODES END----------- - - # Mode list for selecting between low bandwidth ( 500Hz ) and modes with higher bandwidth - # but ability to fall back to low bandwidth modes if needed. - if self.low_bandwidth_mode: - # List of codec2 modes to use in "low bandwidth" mode. - self.mode_list = self.mode_list_low_bw - # list of times to wait for corresponding mode in seconds - self.time_list = self.time_list_low_bw - - else: - # List of codec2 modes to use in "high bandwidth" mode. - self.mode_list = self.mode_list_high_bw - # list of times to wait for corresponding mode in seconds - self.time_list = self.time_list_high_bw - - self.speed_level = len(self.mode_list) - 1 # speed level for selecting mode - self.states.set("arq_speed_level", self.speed_level) - - - self.is_IRS = False - self.burst_nack = False - self.burst_nack_counter = 0 - self.frame_nack_counter = 0 - self.frame_received_counter = 0 - - - # TIMEOUTS - self.transmission_timeout = 180 # transmission timeout in seconds - self.channel_busy_timeout = 3 # time how long we want to wait until channel busy state overrides - - - - - - - # START THE THREAD FOR THE TIMEOUT WATCHDOG - watchdog_thread = threading.Thread( - target=self.watchdog, name="watchdog", daemon=True - ) - watchdog_thread.start() - - arq_session_thread = threading.Thread( - target=self.heartbeat, name="watchdog", daemon=True - ) - arq_session_thread.start() - - def send_ident_frame(self, transmit) -> None: - """Build and send IDENT frame """ - ident_frame = bytearray(self.length_sig1_frame) - ident_frame[:1] = bytes([FR_TYPE.IDENT.value]) - ident_frame[1:self.length_sig1_frame] = self.mycallsign - - # Transmit frame - if transmit: - self.enqueue_frame_for_tx([ident_frame], c2_mode=FREEDV_MODE.sig0.value) - else: - return ident_frame - - def send_disconnect_frame(self) -> None: - """Build and send a disconnect frame""" - disconnection_frame = bytearray(self.length_sig1_frame) - disconnection_frame[:1] = bytes([FR_TYPE.ARQ_SESSION_CLOSE.value]) - disconnection_frame[1:2] = self.session_id - disconnection_frame[2:5] = self.dxcallsign_crc - - # wait if we have a channel busy condition - if self.states.channel_busy: - self.channel_busy_handler() - - self.enqueue_frame_for_tx([disconnection_frame], c2_mode=FREEDV_MODE.sig0.value, copies=3, repeat_delay=0) - - - def check_if_mode_fits_to_busy_slot(self): - """ - Check if actual mode is fitting into given busy state - - Returns: - - """ - mode_name = FREEDV_MODE(self.mode_list[self.speed_level]).name - mode_slots = FREEDV_MODE_USED_SLOTS[mode_name].value - if mode_slots in [self.states.channel_busy_slot]: - self.log.warning( - "[Modem] busy slot detection", - slots=self.states.channel_busy_slot, - mode_slots=mode_slots, - ) - return False - return True - - def arq_calculate_speed_level(self, snr): - current_speed_level = self.speed_level - self.frame_received_counter += 1 - # try increasing speed level only if we had two successful decodes - if self.frame_received_counter >= 2: - self.frame_received_counter = 0 - - # make sure new speed level isn't higher than available modes - new_speed_level = min(self.speed_level + 1, len(self.mode_list) - 1) - # check if actual snr is higher than minimum snr for next mode - if snr >= self.snr_list[new_speed_level]: - self.speed_level = new_speed_level - - - else: - self.log.info("[Modem] ARQ | increasing speed level not possible because of SNR limit", - given_snr=snr, - needed_snr=self.snr_list[new_speed_level] - ) - - # calculate if speed level fits to busy condition - if not self.check_if_mode_fits_to_busy_slot(): - self.speed_level = current_speed_level - - self.states.set("arq_speed_level", self.speed_level) - - # Update modes we are listening to - self.set_listening_modes(False, True, self.mode_list[self.speed_level]) - - self.log.debug( - "[Modem] calculated speed level", - speed_level=self.speed_level, - given_snr=snr, - min_snr=self.snr_list[self.speed_level], - ) - - - - - - - # for i in range(0, 6, 2): - # if not missing_area[i: i + 2].endswith(b"\x00\x00"): - # self.rpt_request_buffer.insert(0, missing_area[i: i + 2]) - - - - - - ########################################################################################################## - # ARQ DATA CHANNEL HANDLER - ########################################################################################################## - - - - - - def stop_transmission(self) -> None: - """ - Force a stop of the running transmission - """ - self.log.warning("[Modem] Stopping transmission!") - - self.event_manager.send_custom_event( - freedata="modem-message", - arq="transmission", - status="stopped", - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8') - ) - - stop_frame = bytearray(self.length_sig0_frame) - stop_frame[:1] = bytes([FR_TYPE.ARQ_STOP.value]) - stop_frame[1:4] = self.dxcallsign_crc - stop_frame[4:7] = self.mycallsign_crc - # TODO Not sure if we really need the session id when disconnecting - # stop_frame[1:2] = self.session_id - stop_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign) - self.enqueue_frame_for_tx([stop_frame], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) - - self.arq_cleanup() - - def received_stop_transmission( - self, deconstructed_frame: list - ) -> None: # pylint: disable=unused-argument - """ - Received a transmission stop - """ - self.log.warning("[Modem] Stopping transmission!") - self.states.set("is_modem_busy", False) - self.states.set("is_arq_state", False) - self.event_manager.send_custom_event( - freedata="modem-message", - arq="transmission", - status="stopped", - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - uuid=self.transmission_uuid - ) - self.arq_cleanup() - - def channel_busy_handler(self): - """ - function for handling the channel busy situation - Args: - - Returns: - """ - self.log.warning("[Modem] Channel busy, waiting until free...") - self.event_manager.send_custom_event( - freedata="modem-message", - channel="busy", - status="waiting", - ) - - # wait while timeout not reached and our busy state is busy - channel_busy_timeout = time.time() + self.channel_busy_timeout - while self.states.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): - threading.Event().wait(0.01) - - # ------------ CALCULATE TRANSFER RATES - - - def reset_statistics(self) -> None: - """ - Reset statistics - """ - # reset ARQ statistics - self.states.set("bytes_per_minute_burst", 0) - self.states.set("arq_total_bytes", 0) - self.states.set("self.states.arq_seconds_until_finish", 0) - self.states.set("arq_bits_per_second", 0) - self.states.set("bytes_per_minute", 0) - self.states.set("arq_transmission_percent", 0) - self.states.set("arq_compression_factor", 0) - - - # ----------------------CLEANUP AND RESET FUNCTIONS - def arq_cleanup(self) -> None: - """ - Cleanup function which clears all ARQ states - """ - - # TODO - # We need to check if we are in a ARQ session - # Then we cant delete the session_id for now - self.states.delete_arq_instance_by_id(self.session_id) - - - if TESTMODE: - self.log.debug("[Modem] TESTMODE: arq_cleanup: Not performing cleanup.") - return - - self.log.debug("[Modem] arq_cleanup") - # wait a second for smoother arq behaviour - helpers.wait(1.0) - - self.rx_frame_bof_received = False - self.rx_frame_eof_received = False - self.burst_ack = False - self.rpt_request_received = False - self.burst_rpt_counter = 0 - self.data_frame_ack_received = False - self.arq_rx_burst_buffer = [] - self.arq_rx_frame_buffer = b"" - self.burst_ack_snr = 0 - self.arq_burst_last_payload = 0 - self.rx_n_frame_of_burst = 0 - self.rx_n_frames_per_burst = 0 - - # reset modem receiving state to reduce cpu load - modem.demodulator.RECEIVE_SIG0 = True - modem.demodulator.RECEIVE_SIG1 = False - modem.demodulator.RECEIVE_DATAC1 = False - modem.demodulator.RECEIVE_DATAC3 = False - modem.demodulator.RECEIVE_DATAC4 = False - # modem.demodulator.RECEIVE_FSK_LDPC_0 = False - modem.demodulator.RECEIVE_FSK_LDPC_1 = False - - self.is_IRS = False - self.burst_nack = False - self.burst_nack_counter = 0 - self.frame_nack_counter = 0 - self.frame_received_counter = 0 - self.speed_level = len(self.mode_list) - 1 - self.states.set("arq_speed_level", self.speed_level) - - # low bandwidth mode indicator - self.received_LOW_BANDWIDTH_MODE = False - - # reset retry counter for rx channel / burst - self.n_retries_per_burst = 0 - - # reset max retries possibly overriden by api - self.session_connect_max_retries = 10 - self.data_channel_max_retries = 10 - - self.states.set("arq_session_state", "disconnected") - self.states.arq_speed_list = [] - self.states.set("is_arq_state", False) - self.arq_state_event = threading.Event() - self.arq_file_transfer = False - - self.beacon_paused = False - # reset beacon interval timer for not directly starting beacon after ARQ - self.beacon_interval_timer = time.time() + self.beacon_interval - - def arq_reset_ack(self, state: bool) -> None: - """ - Funktion for resetting acknowledge states - Args: - state:bool: - - """ - self.burst_ack = state - self.rpt_request_received = state - self.data_frame_ack_received = state - - def set_listening_modes(self, enable_sig0: bool, enable_sig1: bool, mode: int) -> None: - # sourcery skip: extract-duplicate-method - """ - Function for setting the data modes we are listening to for saving cpu power - - Args: - enable_sig0:int: Enable/Disable signalling mode 0 - enable_sig1:int: Enable/Disable signalling mode 1 - mode:int: Codec2 mode to listen for - - """ - # set modes we want to listen to - modem.RECEIVE_SIG0 = enable_sig0 - modem.RECEIVE_SIG1 = enable_sig1 - - if mode == codec2.FREEDV_MODE.datac1.value: - modem.RECEIVE_DATAC1 = True - modem.RECEIVE_DATAC3 = False - modem.RECEIVE_DATAC4 = False - modem.RECEIVE_FSK_LDPC_1 = False - self.log.debug("[Modem] Changing listening data mode", mode="datac1") - elif mode == codec2.FREEDV_MODE.datac3.value: - modem.RECEIVE_DATAC1 = False - modem.RECEIVE_DATAC3 = True - modem.RECEIVE_DATAC4 = False - modem.RECEIVE_FSK_LDPC_1 = False - self.log.debug("[Modem] Changing listening data mode", mode="datac3") - elif mode == codec2.FREEDV_MODE.datac4.value: - modem.RECEIVE_DATAC1 = False - modem.RECEIVE_DATAC3 = False - modem.RECEIVE_DATAC4 = True - modem.RECEIVE_FSK_LDPC_1 = False - self.log.debug("[Modem] Changing listening data mode", mode="datac4") - elif mode == codec2.FREEDV_MODE.fsk_ldpc_1.value: - modem.RECEIVE_DATAC1 = False - modem.RECEIVE_DATAC3 = False - modem.RECEIVE_DATAC4 = False - modem.RECEIVE_FSK_LDPC_1 = True - self.log.debug("[Modem] Changing listening data mode", mode="fsk_ldpc_1") - else: - modem.RECEIVE_DATAC1 = True - modem.RECEIVE_DATAC3 = True - modem.RECEIVE_DATAC4 = True - modem.RECEIVE_FSK_LDPC_1 = True - self.log.debug( - "[Modem] Changing listening data mode", mode="datac1/datac3/fsk_ldpc" - ) - - # ------------------------- WATCHDOG FUNCTIONS FOR TIMER - def watchdog(self) -> None: - """Author: DJ2LS - - Watchdog master function. From here, "pet" the watchdogs - - """ - while True: - threading.Event().wait(0.1) - self.data_channel_keep_alive_watchdog() - self.burst_watchdog() - self.arq_session_keep_alive_watchdog() - - def burst_watchdog(self) -> None: - """ - Watchdog which checks if we are running into a connection timeout - DATA BURST - """ - # IRS SIDE - # TODO We need to redesign this part for cleaner state handling - # Return if not ARQ STATE and not ARQ SESSION STATE as they are different use cases - if ( - not self.states.is_arq_state - and self.states.arq_session_state != "connected" - or not self.is_IRS - ): - return - - # get modem error state - modem_error_state = modem.get_modem_error_state() - - # We want to reach this state only if connected ( == return above not called ) - if self.rx_n_frames_per_burst > 1: - # uses case for IRS: reduce time for waiting by counting "None" in burst buffer - frames_left = self.arq_rx_burst_buffer.count(None) - elif self.rx_n_frame_of_burst == 0 and self.rx_n_frames_per_burst == 0: - # use case for IRS: We didn't receive a burst yet, because the first one got lost - # in this case we don't have any information about the expected burst length - # we must assume, we are getting a burst with max_n_frames_per_burst - frames_left = self.max_n_frames_per_burst - else: - frames_left = 1 - - # make sure we don't have a 0 here for avoiding too short timeouts - if frames_left == 0: - frames_left = 1 - - # timeout is reached, if we didnt receive data, while we waited - # for the corresponding data frame + the transmitted signalling frame of ack/nack - # + a small offset of about 1 second - timeout = \ - ( - self.burst_last_received - + (self.time_list[self.speed_level] * frames_left) - + self.duration_sig0_frame - + self.channel_busy_timeout - + 1 - ) - - # override calculation - # if we reached 2/3 of the waiting time and didnt received a signal - # then send NACK earlier - time_left = timeout - time.time() - waiting_time = (self.time_list[ - self.speed_level] * frames_left) + self.duration_sig0_frame + self.channel_busy_timeout + 1 - timeout_percent = 100 - (time_left / waiting_time * 100) - # timeout_percent = 0 - if timeout_percent >= 75 and not self.states.is_codec2_traffic and not self.states.isTransmitting(): - override = True - else: - override = False - - # TODO Enable this for development - print( - f"timeout expected in:{round(timeout - time.time())} | timeout percent: {timeout_percent} | frames left: {frames_left} of {self.rx_n_frames_per_burst} | speed level: {self.speed_level}") - # if timeout is expired, but we are receivingt codec2 data, - # better wait some more time because data might be important for us - # reason for this situation can be delays on IRS and ISS, maybe because both had a busy channel condition. - # Nevertheless, we need to keep timeouts short for efficiency - if timeout <= time.time() or modem_error_state and not self.states.is_codec2_traffic and not self.states.isTransmitting() or override: - self.log.warning( - "[Modem] Burst decoding error or timeout", - attempt=self.n_retries_per_burst, - max_attempts=self.rx_n_max_retries_per_burst, - speed_level=self.speed_level, - modem_error_state=modem_error_state - ) - - print( - f"frames_per_burst {self.rx_n_frame_of_burst} / {self.rx_n_frames_per_burst}, Repeats: {self.burst_rpt_counter} Nones: {self.arq_rx_burst_buffer.count(None)}") - # check if we have N frames per burst > 1 - if self.rx_n_frames_per_burst > 1 and self.burst_rpt_counter < 3 and self.arq_rx_burst_buffer.count( - None) > 0: - # reset self.burst_last_received - self.burst_last_received = time.time() + self.time_list[self.speed_level] * frames_left - self.burst_rpt_counter += 1 - self.send_retransmit_request_frame() - - else: - - # reset self.burst_last_received counter - self.burst_last_received = time.time() - - # reduce speed level if nack counter increased - self.frame_received_counter = 0 - self.burst_nack_counter += 1 - if self.burst_nack_counter >= 2: - self.burst_nack_counter = 0 - self.speed_level = max(self.speed_level - 1, 0) - self.states.set("arq_speed_level", self.speed_level) - - # TODO Create better mechanisms for handling n frames per burst for bad channels - # reduce frames per burst - if self.burst_rpt_counter >= 2: - tx_n_frames_per_burst = max(self.rx_n_frames_per_burst - 1, 1) - else: - tx_n_frames_per_burst = self.rx_n_frames_per_burst - - # Update modes we are listening to - self.set_listening_modes(True, True, self.mode_list[self.speed_level]) - - # TODO Does SNR make sense for NACK if we dont have an actual SNR information? - self.send_burst_nack_frame_watchdog(tx_n_frames_per_burst) - - # Update data_channel timestamp - # TODO Disabled this one for testing. - # self.data_channel_last_received = time.time() - self.n_retries_per_burst += 1 - else: - # debugging output - # print((self.data_channel_last_received + self.time_list[self.speed_level])-time.time()) - pass - - if self.n_retries_per_burst >= self.rx_n_max_retries_per_burst: - self.stop_transmission() - - def data_channel_keep_alive_watchdog(self) -> None: - """ - watchdog which checks if we are running into a connection timeout - DATA CHANNEL - """ - # and not static.ARQ_SEND_KEEP_ALIVE: - if self.states.is_arq_state and self.states.is_modem_busy: - threading.Event().wait(0.01) - if ( - self.data_channel_last_received + self.transmission_timeout - > time.time() - ): - - timeleft = int((self.data_channel_last_received + self.transmission_timeout) - time.time()) - self.states.set("arq_seconds_until_timeout", timeleft) - if timeleft % 10 == 0: - self.log.debug("Time left until channel timeout", seconds=timeleft) - - # threading.Event().wait(5) - # print(self.data_channel_last_received + self.transmission_timeout - time.time()) - # pass - else: - # Clear the timeout timestamp - self.data_channel_last_received = 0 - self.log.info( - "[Modem] DATA [" - + str(self.mycallsign, "UTF-8") - + "]<>[" - + str(self.dxcallsign, "UTF-8") - + "]" - ) - self.event_manager.send_custom_event( - freedata="modem-message", - arq="transmission", - status="failed", - uuid=self.transmission_uuid, - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS) - ) - self.arq_cleanup() - - def arq_session_keep_alive_watchdog(self) -> None: - """ - watchdog which checks if we are running into a connection timeout - ARQ SESSION - """ - if ( - self.states.is_arq_session - and self.states.is_modem_busy - and not self.arq_file_transfer - ): - if self.arq_session_last_received + self.arq_session_timeout > time.time(): - threading.Event().wait(0.01) - else: - self.log.info( - "[Modem] SESSION [" - + str(self.mycallsign, "UTF-8") - + "]<>[" - + str(self.dxcallsign, "UTF-8") - + "]" - ) - self.event_manager.send_custom_event( - freedata="modem-message", - arq="session", - status="failed", - reason="timeout", - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - ) - self.close_session() - - def heartbeat(self) -> None: - """ - Heartbeat thread which auto pauses and resumes the heartbeat signal when in an arq session - """ - while True: - threading.Event().wait(0.01) - # additional check for smoother stopping if heartbeat transmission - while not self.arq_file_transfer: - threading.Event().wait(0.01) - if ( - self.states.is_arq_session - and self.IS_ARQ_SESSION_MASTER - and self.states.arq_session_state == "connected" - # and not self.arq_file_transfer - ): - threading.Event().wait(1) - self.transmit_session_heartbeat() - threading.Event().wait(2) - diff --git a/modem/data cemetery/deprecated_protocol_arq_session_irs.py b/modem/data cemetery/deprecated_protocol_arq_session_irs.py deleted file mode 100644 index ea19e4f1..00000000 --- a/modem/data cemetery/deprecated_protocol_arq_session_irs.py +++ /dev/null @@ -1,853 +0,0 @@ - -import base64 -import time -import uuid -import lzma -import helpers -import numpy as np -from codec2 import FREEDV_MODE -from queues import RX_BUFFER -from modem_frametypes import FRAME_TYPE as FR_TYPE - -from deprecated_protocol_arq_session import ARQ - - -class IRS(ARQ): - def __init__(self, config, event_queue, states): - super().__init__(config, event_queue, states) - - - self.arq_rx_burst_buffer = [] - self.arq_rx_frame_buffer = b"" - self.rx_n_max_retries_per_burst = 40 - self.rx_n_frame_of_burst = 0 - self.rx_n_frames_per_burst = 0 - - - self.rx_frame_bof_received = False - self.rx_frame_eof_received = False - - self.rx_start_of_transmission = 0 # time of transmission start - - - # minimum payload for arq burst - # import for avoiding byteorder bug and buffer search area - self.arq_burst_header_size = 3 - self.arq_burst_minimum_payload = 56 - self.arq_burst_header_size - self.arq_burst_maximum_payload = 510 - self.arq_burst_header_size - # save last used payload for optimising buffer search area - self.arq_burst_last_payload = self.arq_burst_maximum_payload - - - - - def arq_process_received_data_frame(self, data_frame, snr, signed): - """ - - - """ - # transmittion duration - - signed = "True" if signed else "False" - - duration = time.time() - self.rx_start_of_transmission - self.calculate_transfer_rate_rx( - self.rx_start_of_transmission, len(self.arq_rx_frame_buffer), snr - ) - self.log.info("[Modem] ARQ | RX | DATA FRAME SUCCESSFULLY RECEIVED", nacks=self.frame_nack_counter, - bytesperminute=self.states.arq_bytes_per_minute, total_bytes=self.states.arq_total_bytes, - duration=duration, hmac_signed=signed) - - # Decompress the data frame - data_frame_decompressed = lzma.decompress(data_frame) - self.arq_compression_factor = len(data_frame_decompressed) / len( - data_frame - ) - data_frame = data_frame_decompressed - - self.transmission_uuid = str(uuid.uuid4()) - timestamp = int(time.time()) - - # Re-code data_frame in base64, UTF-8 for JSON UI communication. - base64_data = base64.b64encode(data_frame).decode("UTF-8") - - # check if RX_BUFFER isn't full - if not RX_BUFFER.full(): - # make sure we have always the correct buffer size - RX_BUFFER.maxsize = int(self.arq_rx_buffer_size) - else: - # if full, free space by getting an item - self.log.info( - "[Modem] ARQ | RX | RX_BUFFER FULL - dropping old data", - buffer_size=RX_BUFFER.qsize(), - maxsize=int(self.arq_rx_buffer_size) - ) - RX_BUFFER.get() - - # add item to RX_BUFFER - self.log.info( - "[Modem] ARQ | RX | saving data to rx buffer", - buffer_size=RX_BUFFER.qsize() + 1, - maxsize=RX_BUFFER.maxsize - ) - try: - # RX_BUFFER[0] = transmission uuid - # RX_BUFFER[1] = timestamp - # RX_BUFFER[2] = dxcallsign - # RX_BUFFER[3] = dxgrid - # RX_BUFFER[4] = data - # RX_BUFFER[5] = hmac signed - # RX_BUFFER[6] = compression factor - # RX_BUFFER[7] = bytes per minute - # RX_BUFFER[8] = duration - # RX_BUFFER[9] = self.frame_nack_counter - # RX_BUFFER[10] = speed list stats - - RX_BUFFER.put( - [ - self.transmission_uuid, - timestamp, - self.dxcallsign, - self.dxgrid, - base64_data, - signed, - self.arq_compression_factor, - self.states.arq_bytes_per_minute, - duration, - self.frame_nack_counter, - self.states.arq_speed_list - ] - ) - except Exception as e: - # File "/usr/lib/python3.7/queue.py", line 133, in put - # if self.maxsize > 0 - # TypeError: '>' not supported between instances of 'str' and 'int' - # - # Occurs on Raspberry Pi and Python 3.7 - self.log.error( - "[Modem] ARQ | RX | error occurred when saving data!", - e=e, - uuid=self.transmission_uuid, - timestamp=timestamp, - dxcall=self.dxcallsign, - dxgrid=self.dxgrid, - data=base64_data - ) - - self.event_manager.send_custom_event( - freedata="modem-message", - arq="transmission", - status="received", - uuid=self.transmission_uuid, - percent=self.states.arq_transmission_percent, - bytesperminute=self.states.arq_bytes_per_minute, - compression=self.arq_compression_factor, - timestamp=timestamp, - finished=0, - mycallsign=str(self.mycallsign, "UTF-8"), - dxcallsign=str(self.dxcallsign, "UTF-8"), - dxgrid=str(self.dxgrid, "UTF-8"), - data=base64_data, - irs=helpers.bool_to_string(self.is_IRS), - hmac_signed=signed, - duration=duration, - nacks=self.frame_nack_counter, - speed_list=self.states.arq_speed_list - ) - - if self.enable_stats: - duration = time.time() - self.rx_start_of_transmission - self.stats.push(frame_nack_counter=self.frame_nack_counter, status="received", duration=duration) - - self.log.info( - "[Modem] ARQ | RX | SENDING DATA FRAME ACK") - - self.send_data_ack_frame(snr) - # Update statistics AFTER the frame ACK is sent - self.calculate_transfer_rate_rx( - self.rx_start_of_transmission, len(self.arq_rx_frame_buffer), snr - ) - - self.log.info( - "[Modem] | RX | DATACHANNEL [" - + str(self.mycallsign, "UTF-8") - + "]<< >>[" - + str(self.dxcallsign, "UTF-8") - + "]", - snr=snr, - ) - - - def arq_received_data_channel_opener(self, data_in: bytes, snr): - """ - Received request to open data channel frame - - Args: - data_in:bytes: - - """ - # We've arrived here from process_data which already checked that the frame - # is intended for this station. - - # stop processing if we don't want to respond to a call when not in a arq session - if not self.respond_to_call and not self.states.is_arq_session: - return False - - # stop processing if not in arq session, but modem state is busy and we have a different session id - # use-case we get a connection request while connecting to another station - if not self.states.is_arq_session and self.states.is_modem_busy and data_in[13:14] != self.session_id: - return False - - self.arq_file_transfer = True - - # check if callsign ssid override - _, self.mycallsign = helpers.check_callsign(self.mycallsign, data_in[1:4], self.ssid_list) - - # ignore channel opener if already in ARQ STATE - # use case: Station A is connecting to Station B while - # Station B already tries connecting to Station A. - # For avoiding ignoring repeated connect request in case of packet loss - # we are only ignoring packets in case we are ISS - if self.arq_state_event.is_set() and not self.is_IRS: - return False - - self.is_IRS = True - - self.dxcallsign_crc = bytes(data_in[4:7]) - self.dxcallsign = helpers.bytes_to_callsign(bytes(data_in[7:13])) - self.states.set("dxcallsign", self.dxcallsign) - - self.event_manager.send_custom_event( - freedata="modem-message", - arq="transmission", - status="opening", - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS) - ) - - frametype = int.from_bytes(bytes(data_in[:1]), "big") - # check if we received low bandwidth mode - # possible channel constellations - # ISS(w) <-> IRS(w) - # ISS(w) <-> IRS(n) - # ISS(n) <-> IRS(w) - # ISS(n) <-> IRS(n) - - if frametype == FR_TYPE.ARQ_DC_OPEN_W.value and not self.low_bandwidth_mode: - # ISS(w) <-> IRS(w) - constellation = "ISS(w) <-> IRS(w)" - self.received_LOW_BANDWIDTH_MODE = False - self.mode_list = self.mode_list_high_bw - self.time_list = self.time_list_high_bw - self.snr_list = self.snr_list_high_bw - elif frametype == FR_TYPE.ARQ_DC_OPEN_W.value: - # ISS(w) <-> IRS(n) - constellation = "ISS(w) <-> IRS(n)" - self.received_LOW_BANDWIDTH_MODE = False - self.mode_list = self.mode_list_low_bw - self.time_list = self.time_list_low_bw - self.snr_list = self.snr_list_low_bw - elif frametype == FR_TYPE.ARQ_DC_OPEN_N.value and not self.low_bandwidth_mode: - # ISS(n) <-> IRS(w) - constellation = "ISS(n) <-> IRS(w)" - self.received_LOW_BANDWIDTH_MODE = True - self.mode_list = self.mode_list_low_bw - self.time_list = self.time_list_low_bw - self.snr_list = self.snr_list_low_bw - elif frametype == FR_TYPE.ARQ_DC_OPEN_N.value: - # ISS(n) <-> IRS(n) - constellation = "ISS(n) <-> IRS(n)" - self.received_LOW_BANDWIDTH_MODE = True - self.mode_list = self.mode_list_low_bw - self.time_list = self.time_list_low_bw - self.snr_list = self.snr_list_low_bw - else: - constellation = "not matched" - self.received_LOW_BANDWIDTH_MODE = True - self.mode_list = self.mode_list_low_bw - self.time_list = self.time_list_low_bw - self.snr_list = self.snr_list_low_bw - - # get mode which fits to given SNR - # initially set speed_level 0 in case of bad SNR and no matching mode - self.speed_level = 0 - - # calculate initial speed level in correlation to latest known SNR - for i in range(len(self.mode_list)): - if snr >= self.snr_list[i]: - self.speed_level = i - - # check if speed level fits to busy condition - if not self.check_if_mode_fits_to_busy_slot(): - self.speed_level = 0 - - # Update modes we are listening to - self.set_listening_modes(True, True, self.mode_list[self.speed_level]) - self.dxgrid = b'------' - helpers.add_to_heard_stations( - self.dxcallsign, - self.dxgrid, - "DATA", - snr, - self.modem_frequency_offset, - self.states.radio_frequency, - self.states.heard_stations - ) - - self.session_id = data_in[13:14] - - # check again if callsign ssid override - _, self.mycallsign = helpers.check_callsign(self.mycallsign, data_in[1:4], self.ssid_list) - - self.log.info( - "[Modem] ARQ | DATA | RX | [" - + str(self.mycallsign, "UTF-8") - + "]>> <<[" - + str(self.dxcallsign, "UTF-8") - + "]", - channel_constellation=constellation, - ) - - # Reset data_channel/burst timestamps - # TIMING TEST - self.data_channel_last_received = int(time.time()) - self.burst_last_received = int(time.time() + 10) # we might need some more time so lets increase this - - # Set ARQ State AFTER resetting timeouts - # this avoids timeouts starting too early - self.states.set("is_arq_state", True) - self.states.set("is_modem_busy", True) - - self.reset_statistics() - - # Select the frame type based on the current Modem mode - if self.low_bandwidth_mode or self.received_LOW_BANDWIDTH_MODE: - frametype = bytes([FR_TYPE.ARQ_DC_OPEN_ACK_N.value]) - self.log.debug("[Modem] Responding with low bandwidth mode") - else: - frametype = bytes([FR_TYPE.ARQ_DC_OPEN_ACK_W.value]) - self.log.debug("[Modem] Responding with high bandwidth mode") - - - connection_ack_frame = self.frame_factory.build_arq_connect_ack( - session_id=self.session_id, - speed_level=self.speed_level, - arq_protocol_version=self.arq_protocol_version - ) - - self.enqueue_frame_for_tx([connection_ack_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) - - self.event_manager.send_custom_event( - freedata="modem-message", - arq="transmission", - status="opened", - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS) - ) - - self.log.info( - "[Modem] ARQ | DATA | RX | [" - + str(self.mycallsign, "UTF-8") - + "]>>|<<[" - + str(self.dxcallsign, "UTF-8") - + "]", - bandwidth="wide", - snr=snr, - ) - - # set start of transmission for our statistics - self.rx_start_of_transmission = time.time() - - # TIMING TEST - # Reset data_channel/burst timestamps once again for avoiding running into timeout - # and therefore sending a NACK - self.data_channel_last_received = int(time.time()) - self.burst_last_received = int(time.time() + 10) # we might need some more time so lets increase this - - def calculate_transfer_rate_rx( - self, rx_start_of_transmission: float, receivedbytes: int, snr - ) -> list: - """ - Calculate transfer rate for received data - Args: - rx_start_of_transmission:float: - receivedbytes:int: - - Returns: List of: - bits_per_second: float, - bytes_per_minute: float, - transmission_percent: float - """ - try: - if self.states.arq_total_bytes == 0: - self.states.set("arq_total_bytes", 1) - arq_transmission_percent = min( - int( - ( - receivedbytes - * self.arq_compression_factor - / self.states.arq_total_bytes - ) - * 100 - ), - 100, - ) - - transmissiontime = time.time() - self.rx_start_of_transmission - - if receivedbytes > 0: - arq_bits_per_second = int((receivedbytes * 8) / transmissiontime) - bytes_per_minute = int( - receivedbytes / (transmissiontime / 60) - ) - arq_seconds_until_finish = int(((self.states.arq_total_bytes - receivedbytes) / ( - bytes_per_minute * self.arq_compression_factor)) * 60) - 20 # offset because of frame ack/nack - - speed_chart = {"snr": snr, "bpm": bytes_per_minute, "timestamp": int(time.time())} - # check if data already in list - if speed_chart not in self.states.arq_speed_list: - self.states.arq_speed_list.append(speed_chart) - else: - arq_bits_per_second = 0 - bytes_per_minute = 0 - arq_seconds_until_finish = 0 - except Exception as err: - self.log.error(f"[Modem] calculate_transfer_rate_rx: Exception: {err}") - arq_transmission_percent = 0.0 - arq_bits_per_second = 0 - bytes_per_minute = 0 - - self.states.set("arq_bits_per_second", arq_bits_per_second) - self.states.set("bytes_per_minute", bytes_per_minute) - self.states.set("arq_transmission_percent", arq_transmission_percent) - self.states.set("arq_compression_factor", self.arq_compression_factor) - - return [ - arq_bits_per_second, - bytes_per_minute, - arq_transmission_percent, - ] - - def send_burst_nack_frame(self, snr: bytes) -> None: - """Build and send NACK frame for received DATA frame""" - - # nack_frame = bytearray(self.length_sig1_frame) - # nack_frame[:1] = bytes([FR_TYPE.FR_NACK.value]) - # nack_frame[1:2] = self.session_id - # nack_frame[2:3] = helpers.snr_to_bytes(snr) - # nack_frame[3:4] = bytes([int(self.speed_level)]) - # nack_frame[4:8] = len(self.arq_rx_frame_buffer).to_bytes(4, byteorder="big") - - - nack_frame = self.frame_factory.build_arq_frame_nack(session_id=self.session_id, - snr=snr, - speed_level=self.speed_level, - len_arq_rx_frame_buffer=len(self.arq_rx_frame_buffer) - ) - # TRANSMIT NACK FRAME FOR BURST - # TODO Do we have to send ident frame? - # self.enqueue_frame_for_tx([ack_frame, self.send_ident_frame(False)], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) - - # wait if we have a channel busy condition - if self.states.channel_busy: - self.channel_busy_handler() - - self.enqueue_frame_for_tx([nack_frame], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) - # reset burst timeout in case we had to wait too long - self.burst_last_received = time.time() - - - def send_burst_nack_frame_watchdog(self, tx_n_frames_per_burst) -> None: - """Build and send NACK frame for watchdog timeout""" - - # increment nack counter for transmission stats - self.frame_nack_counter += 1 - - # we need to clear our rx burst buffer - self.arq_rx_burst_buffer = [] - - # Create and send ACK frame - self.log.info("[Modem] ARQ | RX | SENDING NACK") - # nack_frame = bytearray(self.length_sig1_frame) - # nack_frame[:1] = bytes([FR_TYPE.BURST_NACK.value]) - # nack_frame[1:2] = self.session_id - # nack_frame[2:3] = helpers.snr_to_bytes(0) - # nack_frame[3:4] = bytes([int(self.speed_level)]) - # nack_frame[4:5] = bytes([int(tx_n_frames_per_burst)]) - # nack_frame[5:9] = len(self.arq_rx_frame_buffer).to_bytes(4, byteorder="big") - - nack_frame = self.frame_factory.build_arq_burst_nack(session_id=self.session_id, - snr=0, - speed_level=self.speed_level, - len_arq_rx_frame_buffer=len(self.arq_rx_frame_buffer), - n_frames_per_burst=bytes([int(tx_n_frames_per_burst)]) - ) - - # wait if we have a channel busy condition - if self.states.channel_busy: - self.channel_busy_handler() - - # TRANSMIT NACK FRAME FOR BURST - self.enqueue_frame_for_tx([nack_frame], c2_mode=FREEDV_MODE.sig1.value, copies=1, repeat_delay=0) - - # reset frame counter for not increasing speed level - self.frame_received_counter = 0 - - def arq_data_received( - self, deconstructed_frame: list, bytes_per_frame: int, snr: float, freedv - ) -> None: - """ - Args: - data_in:bytes: - bytes_per_frame:int: - snr:float: - freedv: - - Returns: - """ - # We've arrived here from process_data which already checked that the frame - # is intended for this station. - data_in = deconstructed_frame["data"] - - # only process data if we are in ARQ and BUSY state else return to quit - if not self.states.is_arq_state and not self.states.is_modem_busy: - self.log.warning("[Modem] wrong modem state - dropping data", is_arq_state=self.states.is_arq_state, - modem_state=self.states.is_modem_busy) - return - - self.arq_file_transfer = True - - self.states.set("is_modem_busy", True) - self.states.set("is_arq_state", True) - - # Update data_channel timestamp - self.data_channel_last_received = int(time.time()) - self.burst_last_received = int(time.time()) - - # Extract some important data from the frame - # Get sequence number of burst frame - self.rx_n_frame_of_burst = int.from_bytes(bytes(data_in[:1]), "big") - 10 - # Get number of bursts from received frame - self.rx_n_frames_per_burst = int.from_bytes(bytes(data_in[1:2]), "big") - - # The RX burst buffer needs to have a fixed length filled with "None". - # We need this later for counting the "Nones" to detect missing data. - # Check if burst buffer has expected length else create it - if len(self.arq_rx_burst_buffer) != self.rx_n_frames_per_burst: - self.arq_rx_burst_buffer = [None] * self.rx_n_frames_per_burst - - # Append data to rx burst buffer - self.arq_rx_burst_buffer[self.rx_n_frame_of_burst] = data_in[self.arq_burst_header_size:] # type: ignore - - self.dxgrid = b'------' - helpers.add_to_heard_stations( - self.dxcallsign, - self.dxgrid, - "DATA", - snr, - self.modem_frequency_offset, - self.states.radio_frequency, - self.states.heard_stations - ) - - # Check if we received all frames in the burst by checking if burst buffer has no more "Nones" - # This is the ideal case because we received all data - if None not in self.arq_rx_burst_buffer: - # then iterate through burst buffer and stick the burst together - # the temp burst buffer is needed for checking, if we already received data - temp_burst_buffer = b"" - for value in self.arq_rx_burst_buffer: - # self.arq_rx_frame_buffer += self.arq_rx_burst_buffer[i] - temp_burst_buffer += bytes(value) # type: ignore - - # free up burst buffer - self.arq_rx_burst_buffer = [] - - # TODO Needs to be removed as soon as mode error is fixed - # catch possible modem error which leads into false byteorder - # modem possibly decodes too late - data then is pushed to buffer - # which leads into wrong byteorder - # Lets put this in try/except so we are not crashing modem as its highly experimental - # This might only work for datac1 and datac3 - try: - # area_of_interest = (modem.get_bytes_per_frame(self.mode_list[speed_level] - 1) -3) * 2 - if self.arq_rx_frame_buffer.endswith(temp_burst_buffer[:246]) and len(temp_burst_buffer) >= 246: - self.log.warning( - "[Modem] ARQ | RX | wrong byteorder received - dropping data" - ) - # we need to run a return here, so we are not sending an ACK - # return - except Exception as e: - self.log.warning( - "[Modem] ARQ | RX | wrong byteorder check failed", e=e - ) - - self.log.debug("[Modem] temp_burst_buffer", buffer=temp_burst_buffer) - self.log.debug("[Modem] self.arq_rx_frame_buffer", buffer=self.arq_rx_frame_buffer) - - # if frame buffer ends not with the current frame, we are going to append new data - # if data already exists, we received the frame correctly, - # but the ACK frame didn't receive its destination (ISS) - if self.arq_rx_frame_buffer.endswith(temp_burst_buffer): - self.log.info( - "[Modem] ARQ | RX | Frame already received - sending ACK again" - ) - - else: - # Here we are going to search for our data in the last received bytes. - # This reduces the chance we will lose the entire frame in the case of signalling frame loss - - # self.arq_rx_frame_buffer --> existing data - # temp_burst_buffer --> new data - # search_area --> area where we want to search - - search_area = self.arq_burst_last_payload * self.rx_n_frames_per_burst - - search_position = len(self.arq_rx_frame_buffer) - search_area - # if search position < 0, then search position = 0 - search_position = max(0, search_position) - - # find position of data. returns -1 if nothing found in area else >= 0 - # we are beginning from the end, so if data exists twice or more, - # only the last one should be replaced - # we are going to only check position against minimum data frame payload - # use case: receive data, which already contains received data - # while the payload of data received before is shorter than actual payload - get_position = self.arq_rx_frame_buffer[search_position:].rfind( - temp_burst_buffer[:self.arq_burst_minimum_payload] - ) - # if we find data, replace it at this position with the new data and strip it - if get_position >= 0: - self.arq_rx_frame_buffer = self.arq_rx_frame_buffer[ - : search_position + get_position - ] - self.log.warning( - "[Modem] ARQ | RX | replacing existing buffer data", - area=search_area, - pos=get_position, - ) - else: - self.log.debug("[Modem] ARQ | RX | appending data to buffer") - - self.arq_rx_frame_buffer += temp_burst_buffer - - self.arq_burst_last_payload = len(temp_burst_buffer) - - # Check if we didn't receive a BOF and EOF yet to avoid sending - # ack frames if we already received all data - if ( - not self.rx_frame_bof_received - and not self.rx_frame_eof_received - and data_in.find(self.data_frame_eof) < 0 - ): - self.arq_calculate_speed_level(snr) - - # TIMING TEST - # self.data_channel_last_received = int(time.time()) + 6 + 6 - # self.burst_last_received = int(time.time()) + 6 + 6 - self.data_channel_last_received = int(time.time()) - self.burst_last_received = int(time.time()) - - # Create and send ACK frame - self.log.info("[Modem] ARQ | RX | SENDING ACK", finished=self.states.arq_seconds_until_finish, - bytesperminute=self.states.arq_bytes_per_minute) - - self.send_burst_ack_frame(snr) - - # Reset n retries per burst counter - self.n_retries_per_burst = 0 - - # calculate statistics - self.calculate_transfer_rate_rx( - self.rx_start_of_transmission, len(self.arq_rx_frame_buffer), snr - ) - - # send a network message with information - self.event_manager.send_custom_event( - freedata="modem-message", - arq="transmission", - status="receiving", - uuid=self.transmission_uuid, - percent=self.states.arq_transmission_percent, - bytesperminute=self.states.arq_bytes_per_minute, - compression=self.arq_compression_factor, - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - finished=self.states.arq_seconds_until_finish, - irs=helpers.bool_to_string(self.is_IRS) - ) - else: - self.log.warning( - "[Modem] data_handler: missing data in burst buffer...", - frame=self.rx_n_frame_of_burst + 1, - frames=self.rx_n_frames_per_burst - ) - - # We have a BOF and EOF flag in our data. If we received both we received our frame. - # In case of loosing data, but we received already a BOF and EOF we need to make sure, we - # received the complete last burst by checking it for Nones - bof_position = self.arq_rx_frame_buffer.find(self.data_frame_bof) - eof_position = self.arq_rx_frame_buffer.find(self.data_frame_eof) - - # get total bytes per transmission information as soon we received a frame with a BOF - - if bof_position >= 0: - self.arq_extract_statistics_from_data_frame(bof_position, eof_position, snr) - if ( - bof_position >= 0 - and eof_position > 0 - and None not in self.arq_rx_burst_buffer - ): - self.log.debug( - "[Modem] arq_data_received:", - bof_position=bof_position, - eof_position=eof_position, - ) - self.rx_frame_bof_received = True - self.rx_frame_eof_received = True - - # Extract raw data from buffer - payload = self.arq_rx_frame_buffer[ - bof_position + len(self.data_frame_bof): eof_position - ] - # Get the data frame crc - data_frame_crc = payload[:4] # 0:4 = 4 bytes - # Get the data frame length - frame_length = int.from_bytes(payload[4:8], "big") # 4:8 = 4 bytes - self.states.set("arq_total_bytes", frame_length) - # 8:9 = compression factor - - data_frame = payload[9:] - data_frame_crc_received = helpers.get_crc_32(data_frame) - - # check if hmac signing enabled - if self.enable_hmac: - self.log.info( - "[Modem] [HMAC] Enabled", - ) - # now check if we have valid hmac signature - returns salt or bool - salt_found = helpers.search_hmac_salt(self.dxcallsign, self.mycallsign, data_frame_crc, data_frame, - token_iters=100) - - if salt_found: - # hmac digest received - self.arq_process_received_data_frame(data_frame, snr, signed=True) - - else: - - # hmac signature wrong - self.arq_process_received_data_frame(data_frame, snr, signed=False) - elif data_frame_crc == data_frame_crc_received: - self.log.warning( - "[Modem] [HMAC] Disabled, using CRC", - ) - self.arq_process_received_data_frame(data_frame, snr, signed=False) - else: - self.event_manager.send_custom_event( - freedata="modem-message", - arq="transmission", - status="failed", - uuid=self.transmission_uuid, - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS) - ) - - duration = time.time() - self.rx_start_of_transmission - self.log.warning( - "[Modem] ARQ | RX | DATA FRAME NOT SUCCESSFULLY RECEIVED!", - e="wrong crc", - expected=data_frame_crc.hex(), - received=data_frame_crc_received.hex(), - nacks=self.frame_nack_counter, - duration=duration, - bytesperminute=self.states.arq_bytes_per_minute, - compression=self.arq_compression_factor, - data=data_frame, - - ) - if self.enable_stats: - self.stats.push(frame_nack_counter=self.frame_nack_counter, status="wrong_crc", duration=duration) - - self.log.info("[Modem] ARQ | RX | Sending NACK", finished=self.states.arq_seconds_until_finish, - bytesperminute=self.states.arq_bytes_per_minute) - self.send_burst_nack_frame(snr) - - # Update arq_session timestamp - self.arq_session_last_received = int(time.time()) - - # Finally cleanup our buffers and states, - self.arq_cleanup() - - def arq_extract_statistics_from_data_frame(self, bof_position, eof_position, snr): - payload = self.arq_rx_frame_buffer[ - bof_position + len(self.data_frame_bof): eof_position - ] - frame_length = int.from_bytes(payload[4:8], "big") # 4:8 4bytes - self.states.set("arq_total_bytes", frame_length) - compression_factor = int.from_bytes(payload[8:9], "big") # 4:8 4bytes - # limit to max value of 255 - compression_factor = np.clip(compression_factor, 0, 255) - self.arq_compression_factor = compression_factor / 10 - self.calculate_transfer_rate_rx( - self.rx_start_of_transmission, len(self.arq_rx_frame_buffer), snr - ) - def send_burst_ack_frame(self, snr) -> None: - """Build and send ACK frame for burst DATA frame""" - - # ack_frame = bytearray(self.length_sig1_frame) - # ack_frame[:1] = bytes([FR_TYPE.BURST_ACK.value]) - # ack_frame[1:2] = self.session_id - # ack_frame[2:3] = helpers.snr_to_bytes(snr) - # ack_frame[3:4] = bytes([int(self.speed_level)]) - # ack_frame[4:8] = len(self.arq_rx_frame_buffer).to_bytes(4, byteorder="big") - - ack_frame = self.frame_factory.build_arq_burst_ack(session_id=self.session_id, - snr=snr, - speed_level=self.speed_level, - len_arq_rx_frame_buffer=len(self.arq_rx_frame_buffer) - ) - - # wait if we have a channel busy condition - if self.states.channel_busy: - self.channel_busy_handler() - - # Transmit frame - self.enqueue_frame_for_tx([ack_frame], c2_mode=FREEDV_MODE.sig1.value) - - def send_data_ack_frame(self, snr) -> None: - """Build and send ACK frame for received DATA frame""" - - # ack_frame = bytearray(self.length_sig1_frame) - # ack_frame[:1] = bytes([FR_TYPE.FR_ACK.value]) - # ack_frame[1:2] = self.session_id - # ack_frame[2:3] = helpers.snr_to_bytes(snr) - - ack_frame = self.frame_factory.build_arq_frame_ack(session_id=self.session_id, snr=snr) - # wait if we have a channel busy condition - if self.states.channel_busy: - self.channel_busy_handler() - - # Transmit frame - self.enqueue_frame_for_tx([ack_frame], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) - - - def send_retransmit_request_frame(self) -> None: - # check where a None is in our burst buffer and do frame+1, because lists start at 0 - # FIXME Check to see if there's a `frame - 1` in the receive portion. Remove both if there is. - missing_frames = [ - frame + 1 - for frame, element in enumerate(self.arq_rx_burst_buffer) - if element is None - ] - - rpt_frame = bytearray(self.length_sig1_frame) - rpt_frame[:1] = bytes([FR_TYPE.FR_REPEAT.value]) - rpt_frame[1:2] = self.session_id - rpt_frame[2:2 + len(missing_frames)] = missing_frames - - self.log.info("[Modem] ARQ | RX | Requesting", frames=missing_frames) - # Transmit frame - self.enqueue_frame_for_tx([rpt_frame], c2_mode=FREEDV_MODE.sig1.value, copies=1, repeat_delay=0) diff --git a/modem/data cemetery/deprecated_protocol_arq_session_iss.py b/modem/data cemetery/deprecated_protocol_arq_session_iss.py deleted file mode 100644 index 9d85d7f3..00000000 --- a/modem/data cemetery/deprecated_protocol_arq_session_iss.py +++ /dev/null @@ -1,938 +0,0 @@ - -import sys -import threading -import time -import lzma -from random import randrange -import hmac -import hashlib -import helpers -import modem -import numpy as np -from codec2 import FREEDV_MODE -from modem_frametypes import FRAME_TYPE as FR_TYPE -import event_manager - -from deprecated_protocol_arq_session import ARQ - -class ISS(ARQ): - def __init__(self, config, event_queue, states): - super().__init__(config, event_queue, states) - - self.tx_n_max_retries_per_burst = 40 - self.datachannel_opening_interval = self.duration_sig1_frame + self.channel_busy_timeout + 1 # time between attempts when opening data channel - self.irs_buffer_position = 0 - # actual n retries of burst - self.tx_n_retry_of_burst = 0 - self.burst_ack_snr = 0 # SNR from received burst ack frames - - - - def arq_transmit(self, data_out: bytes, hmac_salt: bytes): - """ - Transmit ARQ frame - - Args: - data_out:bytes: - - - """ - - # set signalling modes we want to listen to - # we are in an ongoing arq transmission, so we don't need sig0 actually - modem.demodulator.RECEIVE_SIG0 = False - modem.demodulator.RECEIVE_SIG1 = True - - self.tx_n_retry_of_burst = 0 # retries we already sent data - # Maximum number of retries to send before declaring a frame is lost - - # save len of data_out to TOTAL_BYTES for our statistics - self.states.set("arq_total_bytes", len(data_out)) - - self.arq_file_transfer = True - frame_total_size = len(data_out).to_bytes(4, byteorder="big") - - # Compress data frame - data_frame_compressed = lzma.compress(data_out) - compression_factor = len(data_out) / len(data_frame_compressed) - self.arq_compression_factor = np.clip(compression_factor, 0, 255) - compression_factor = bytes([int(self.arq_compression_factor * 10)]) - - self.event_manager.send_custom_event( - freedata="modem-message", - arq="transmission", - status="transmitting", - uuid=self.transmission_uuid, - percent=self.states.arq_transmission_percent, - bytesperminute=self.states.arq_bytes_per_minute, - compression=self.arq_compression_factor, - finished=self.states.arq_seconds_until_finish, - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS) - ) - - self.log.info( - "[Modem] | TX | DATACHANNEL", - Bytes=self.states.arq_total_bytes, - ) - - data_out = data_frame_compressed - - # Reset data transfer statistics - tx_start_of_transmission = time.time() - self.calculate_transfer_rate_tx(tx_start_of_transmission, 0, len(data_out)) - - # check if hmac signature is available - if hmac_salt not in ['', False]: - print(data_out) - # create hmac digest - hmac_digest = hmac.new(hmac_salt, data_out, hashlib.sha256).digest() - # truncate to 32bit - frame_payload_crc = hmac_digest[:4] - self.log.debug("[Modem] frame payload HMAC:", crc=frame_payload_crc.hex()) - - else: - # Append a crc at the beginning and end of file indicators - frame_payload_crc = helpers.get_crc_32(data_out) - self.log.debug("[Modem] frame payload CRC:", crc=frame_payload_crc.hex()) - - # Assemble the data frame - data_out = ( - self.data_frame_bof - + frame_payload_crc - + frame_total_size - + compression_factor - + data_out - + self.data_frame_eof - ) - self.log.debug("[Modem] frame raw data:", data=data_out) - # Initial bufferposition is 0 - bufferposition = 0 - bufferposition_end = 0 - bufferposition_burst_start = 0 - - # Iterate through data_out buffer - while not self.data_frame_ack_received and self.states.is_arq_state: - # we have self.tx_n_max_retries_per_burst attempts for sending a burst - for self.tx_n_retry_of_burst in range(self.tx_n_max_retries_per_burst): - # Bound speed level to: - # - minimum of either the speed or the length of mode list - 1 - # - maximum of either the speed or zero - self.speed_level = min(self.speed_level, len(self.mode_list) - 1) - self.speed_level = max(self.speed_level, 0) - - self.states.set("arq_speed_level", self.speed_level) - data_mode = self.mode_list[self.speed_level] - - self.log.debug( - "[Modem] Speed-level:", - level=self.speed_level, - retry=self.tx_n_retry_of_burst, - mode=FREEDV_MODE(data_mode).name, - ) - - # Payload information - payload_per_frame = modem.get_bytes_per_frame(data_mode) - 2 - - self.log.info("[Modem] early buffer info", - bufferposition=bufferposition, - bufferposition_end=bufferposition_end, - bufferposition_burst_start=bufferposition_burst_start - ) - - # check for maximum frames per burst for remaining data - n_frames_per_burst = 1 - if self.max_n_frames_per_burst > 1: - while (payload_per_frame * n_frames_per_burst) % len(data_out[bufferposition_burst_start:]) == ( - payload_per_frame * n_frames_per_burst): - threading.Event().wait(0.01) - # print((payload_per_frame * n_frames_per_burst) % len(data_out)) - n_frames_per_burst += 1 - if n_frames_per_burst == self.max_n_frames_per_burst: - break - else: - n_frames_per_burst = 1 - self.log.info("[Modem] calculated frames_per_burst:", n=n_frames_per_burst) - - tempbuffer = [] - self.rpt_request_buffer = [] - # Append data frames with n_frames_per_burst to tempbuffer - for n_frame in range(n_frames_per_burst): - arqheader = bytearray() - arqheader[:1] = bytes([FR_TYPE.BURST_01.value + n_frame]) - #####arqheader[:1] = bytes([FR_TYPE.BURST_01.value]) - arqheader[1:2] = bytes([n_frames_per_burst]) - arqheader[2:3] = self.session_id - - # only check for buffer position if at least one NACK received - self.log.info("[Modem] ----- data buffer position:", iss_buffer_pos=bufferposition, - irs_bufferposition=self.irs_buffer_position) - if self.frame_nack_counter > 0 and self.irs_buffer_position != bufferposition: - self.log.error("[Modem] ----- data buffer offset:", iss_buffer_pos=bufferposition, - irs_bufferposition=self.irs_buffer_position) - # only adjust buffer position for experimental versions - if self.enable_experimental_features: - self.log.warning("[Modem] ----- data adjustment enabled!") - bufferposition = self.irs_buffer_position - - bufferposition_end = bufferposition + payload_per_frame - len(arqheader) - - # Normal condition - if bufferposition_end <= len(data_out): - frame = data_out[bufferposition:bufferposition_end] - frame = arqheader + frame - - # Pad the last bytes of a frame - else: - extended_data_out = data_out[bufferposition:] - extended_data_out += bytes([0]) * ( - payload_per_frame - len(extended_data_out) - len(arqheader) - ) - frame = arqheader + extended_data_out - - ######tempbuffer = frame # [frame] - tempbuffer.append(frame) - # add data to our repeat request buffer for easy access if we received a request - self.rpt_request_buffer.append(frame) - # set new buffer position - bufferposition = bufferposition_end - - self.log.debug("[Modem] tempbuffer:", tempbuffer=tempbuffer) - self.log.info( - "[Modem] ARQ | TX | FRAMES", - mode=FREEDV_MODE(data_mode).name, - fpb=n_frames_per_burst, - retry=self.tx_n_retry_of_burst, - ) - - self.enqueue_frame_for_tx(tempbuffer, c2_mode=data_mode) - - # After transmission finished, wait for an ACK or RPT frame - while ( - self.states.is_arq_state - and not self.burst_ack - and not self.burst_nack - and not self.rpt_request_received - and not self.data_frame_ack_received - ): - threading.Event().wait(0.01) - - # Once we receive a burst ack, reset its state and break the RETRIES loop - if self.burst_ack: - self.burst_ack = False # reset ack state - self.tx_n_retry_of_burst = 0 # reset retries - self.log.debug( - "[Modem] arq_transmit: Received BURST ACK. Sending next chunk." - , irs_snr=self.burst_ack_snr) - # update temp bufferposition for n frames per burst early calculation - bufferposition_burst_start = bufferposition_end - break # break retry loop - - if self.data_frame_ack_received: - self.log.debug( - "[Modem] arq_transmit: Received FRAME ACK. Braking retry loop." - ) - break # break retry loop - - if self.burst_nack: - self.tx_n_retry_of_burst += 1 - - self.log.warning( - "[Modem] arq_transmit: Received BURST NACK. Resending data", - bufferposition_burst_start=bufferposition_burst_start, - bufferposition=bufferposition - ) - - bufferposition = bufferposition_burst_start - self.burst_nack = False # reset nack state - - # We need this part for leaving the repeat loop - # self.states.is_arq_state == "DATA" --> when stopping transmission manually - if not self.states.is_arq_state: - self.log.debug( - "[Modem] arq_transmit: ARQ State changed to FALSE. Breaking retry loop." - ) - break - - self.calculate_transfer_rate_tx( - tx_start_of_transmission, bufferposition_end, len(data_out) - ) - # NEXT ATTEMPT - self.log.debug( - "[Modem] ATTEMPT:", - retry=self.tx_n_retry_of_burst, - maxretries=self.tx_n_max_retries_per_burst, - ) - - # update buffer position - bufferposition = bufferposition_end - - # update stats - self.calculate_transfer_rate_tx( - tx_start_of_transmission, bufferposition_end, len(data_out) - ) - - self.event_manager.send_custom_event( - freedata="modem-message", - arq="transmission", - status="transmitting", - uuid=self.transmission_uuid, - percent=self.states.arq_transmission_percent, - bytesperminute=self.states.arq_bytes_per_minute, - compression=self.arq_compression_factor, - finished=self.states.arq_seconds_until_finish, - irs_snr=self.burst_ack_snr, - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS) - ) - - # Stay in the while loop until we receive a data_frame_ack. Otherwise, - # the loop exits after sending the last frame only once and doesn't - # wait for an acknowledgement. - if self.data_frame_ack_received and bufferposition > len(data_out): - self.log.debug("[Modem] arq_tx: Last fragment sent and acknowledged.") - break - # GOING TO NEXT ITERATION - - if self.data_frame_ack_received: - self.arq_transmit_success() - else: - self.arq_transmit_failed() - - if TESTMODE: - # Quit after transmission - self.log.debug("[Modem] TESTMODE: arq_transmit exiting.") - sys.exit(0) - - def arq_transmit_success(self): - """ - will be called if we successfully transmitted all of queued data - - """ - # we need to wait until sending "transmitted" state - # gui database is too slow for handling this within 0.001 seconds - # so let's sleep a little - threading.Event().wait(0.2) - self.event_manager.send_custom_event( - freedata="modem-message", - arq="transmission", - status="transmitted", - uuid=self.transmission_uuid, - percent=self.states.arq_transmission_percent, - bytesperminute=self.states.arq_bytes_per_minute, - compression=self.arq_compression_factor, - finished=self.states.arq_seconds_until_finish, - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS), - nacks=self.frame_nack_counter, - speed_list=self.states.arq_speed_list - ) - - self.log.info( - "[Modem] ARQ | TX | DATA TRANSMITTED!", - BytesPerMinute=self.states.arq_bytes_per_minute, - total_bytes=self.states.arq_total_bytes, - BitsPerSecond=self.states.arq_bits_per_second, - ) - - # finally do an arq cleanup - self.arq_cleanup() - - def arq_transmit_failed(self): - """ - will be called if we not successfully transmitted all of queued data - """ - self.event_manager.send_custom_event( - freedata="modem-message", - arq="transmission", - status="failed", - uuid=self.transmission_uuid, - percent=self.states.arq_transmission_percent, - bytesperminute=self.states.arq_bytes_per_minute, - compression=self.arq_compression_factor, - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS), - nacks=self.frame_nack_counter, - speed_list=self.states.arq_speed_list - ) - - self.log.info( - "[Modem] ARQ | TX | TRANSMISSION FAILED OR TIME OUT!") - - self.stop_transmission() - - def burst_ack_nack_received(self, data_in: bytes, snr) -> None: - """ - Received an ACK/NACK for a transmitted frame, keep track and - make adjustments to speed level if needed. - - Args: - data_in:bytes: - - Returns: - - """ - # Process data only if we are in ARQ and BUSY state - if self.states.is_arq_state: - self.dxgrid = b'------' - helpers.add_to_heard_stations( - self.dxcallsign, - self.dxgrid, - "DATA", - snr, - self.modem_frequency_offset, - self.states.radio_frequency, - self.states.heard_stations - ) - - frametype = int.from_bytes(bytes(data_in[:1]), "big") - if frametype == FR_TYPE.BURST_ACK.value: - # Increase speed level if we received a burst ack - # self.speed_level = min(self.speed_level + 1, len(self.mode_list) - 1) - # Force data retry loops of TX Modem to stop and continue with next frame - self.burst_ack = True - # Reset burst nack counter - self.burst_nack_counter = 0 - # Reset n retries per burst counter - self.n_retries_per_burst = 0 - self.irs_buffer_position = int.from_bytes(data_in[4:8], "big") - - self.burst_ack_snr = helpers.snr_from_bytes(data_in[2:3]) - else: - - # Decrease speed level if we received a burst nack - # self.speed_level = max(self.speed_level - 1, 0) - # Set flag to retry frame again. - self.burst_nack = True - # Increment burst nack counter - self.burst_nack_counter += 1 - self.burst_ack_snr = 'NaN' - self.irs_buffer_position = int.from_bytes(data_in[5:9], "big") - - self.log.warning( - "[Modem] ARQ | TX | Burst NACK received", - burst_nack_counter=self.burst_nack_counter, - irs_buffer_position=self.irs_buffer_position, - ) - - # Update data_channel timestamp - self.data_channel_last_received = int(time.time()) - # self.burst_ack_snr = int.from_bytes(bytes(data_in[2:3]), "big") - self.burst_ack_snr = helpers.snr_from_bytes(data_in[2:3]) - # self.log.info("SNR ON IRS", snr=self.burst_ack_snr) - - self.speed_level = int.from_bytes(bytes(data_in[3:4]), "big") - self.states.set("arq_speed_level", self.speed_level) - - def frame_ack_received( - self, data_in: bytes, snr # pylint: disable=unused-argument, - ) -> None: - """Received an ACK for a transmitted frame""" - # Process data only if we are in ARQ and BUSY state - if self.states.is_arq_state: - self.dxgrid = b'------' - helpers.add_to_heard_stations( - self.dxcallsign, - self.dxgrid, - "DATA", - snr, - self.modem_frequency_offset, - self.states.radio_frequency, - self.states.heard_stations - ) - # Force data loops of Modem to stop and continue with next frame - self.data_frame_ack_received = True - # Update arq_session and data_channel timestamp - self.data_channel_last_received = int(time.time()) - self.arq_session_last_received = int(time.time()) - - def frame_nack_received( - self, data_in: bytes, snr # pylint: disable=unused-argument - ) -> None: - """ - Received a NACK for a transmitted frame - - Args: - data_in:bytes: - - """ - self.log.warning("[Modem] ARQ FRAME NACK RECEIVED - cleanup!", - arq="transmission", - status="failed", - uuid=self.transmission_uuid, - percent=self.states.arq_transmission_percent, - bytesperminute=self.states.arq_bytes_per_minute, - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS), - nacks=self.frame_nack_counter, - speed_list=self.states.arq_speed_list - ) - - self.dxgrid = b'------' - helpers.add_to_heard_stations( - self.dxcallsign, - self.dxgrid, - "DATA", - snr, - self.modem_frequency_offset, - self.states.radio_frequency, - self.states.heard_stations - ) - self.event_manager.send_custom_event( - freedata="modem-message", - arq="transmission", - status="failed", - uuid=self.transmission_uuid, - percent=self.states.arq_transmission_percent, - bytesperminute=self.states.arq_bytes_per_minute, - compression=self.arq_compression_factor, - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS), - nacks=self.frame_nack_counter, - speed_list=self.states.arq_speed_list - ) - # Update data_channel timestamp - self.arq_session_last_received = int(time.time()) - - self.arq_cleanup() - - def burst_rpt_received(self, data_in: bytes, snr): - """ - Repeat request frame received for transmitted frame - - Args: - data_in:bytes: - - """ - # Only process data if we are in ARQ and BUSY state - if not self.states.is_arq_state or not self.states.is_modem_busy: - return - self.dxgrid = b'------' - helpers.add_to_heard_stations( - self.dxcallsign, - self.dxgrid, - "DATA", - snr, - self.modem_frequency_offset, - self.states.radio_frequency, - self.states.heard_stations - ) - - self.log.info("[Modem] ARQ REPEAT RECEIVED") - - # self.rpt_request_received = True - # Update data_channel timestamp - self.data_channel_last_received = int(time.time()) - # self.rpt_request_buffer = [] - - missing_area = bytes(data_in[2:12]) # 1:9 - missing_area = missing_area.strip(b"\x00") - print(missing_area) - print(self.rpt_request_buffer) - - tempbuffer_rptframes = [] - for i in range(len(missing_area)): - print(missing_area[i]) - missing_frames_buffer_position = missing_area[i] - 1 - tempbuffer_rptframes.append(self.rpt_request_buffer[missing_frames_buffer_position]) - - self.log.info("[Modem] SENDING REPEAT....") - data_mode = self.mode_list[self.speed_level] - self.enqueue_frame_for_tx(tempbuffer_rptframes, c2_mode=data_mode) - - -############################################################################################################ - # ARQ SESSION HANDLER - ############################################################################################################ - def arq_session_handler(self, mycallsign, dxcallsign) -> bool: - """ - Create a session with `self.dxcallsign` and wait until the session is open. - - Returns: - True if the session was opened successfully - False if the session open request failed - """ - - encoded_call = helpers.callsign_to_bytes(mycallsign) - mycallsign = helpers.bytes_to_callsign(encoded_call) - - encoded_call = helpers.callsign_to_bytes(dxcallsign) - dxcallsign = helpers.bytes_to_callsign(encoded_call) - - self.states.set("dxcallsign", dxcallsign) - dxcallsign_crc = helpers.get_crc_24(dxcallsign) - - self.log.info( - "[Modem] SESSION [" - + str(mycallsign, "UTF-8") - + "]>> <<[" - + str(dxcallsign, "UTF-8") - + "]", - self.states.arq_session_state, - ) - - # wait if we have a channel busy condition - if self.states.channel_busy: - self.channel_busy_handler() - - self.open_session() - - # wait until data channel is open - while not self.states.is_arq_session and not self.arq_session_timeout: - threading.Event().wait(0.01) - self.states.set("arq_session_state", "connecting") - self.event_manager.send_custom_event( - freedata="modem-message", - arq="session", - status="connecting", - mycallsign=str(mycallsign, 'UTF-8'), - dxcallsign=str(dxcallsign, 'UTF-8'), - ) - if self.states.is_arq_session and self.states.arq_session_state == "connected": - # self.states.set("arq_session_state", "connected") - self.event_manager.send_custom_event( - freedata="modem-message", - arq="session", - status="connected", - mycallsign=str(mycallsign, 'UTF-8'), - dxcallsign=str(dxcallsign, 'UTF-8'), - ) - return True - - self.log.warning( - "[Modem] SESSION FAILED [" - + str(mycallsign, "UTF-8") - + "]>>X<<[" - + str(dxcallsign, "UTF-8") - + "]", - attempts=self.session_connect_max_retries, # Adjust for 0-based for user display - reason="maximum connection attempts reached", - state=self.states.arq_session_state, - ) - self.states.set("arq_session_state", "failed") - self.event_manager.send_custom_event( - freedata="modem-message", - arq="session", - status="failed", - reason="timeout", - mycallsign=str(mycallsign, 'UTF-8'), - dxcallsign=str(dxcallsign, 'UTF-8'), - ) - return False - - - def open_dc_and_transmit( - self, - data_out: bytes, - transmission_uuid: str, - mycallsign, - dxcallsign, - ) -> bool: - """ - Open data channel and transmit data - - Args: - data_out:bytes: - transmission_uuid:str: - mycallsign:bytes: - - Returns: - True if the data session was opened and the data was sent - False if the data session was not opened - """ - self.mycallsign = 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 - if not self.states.is_arq_session: - dxcallsign = helpers.callsign_to_bytes(dxcallsign) - dxcallsign = helpers.bytes_to_callsign(dxcallsign) - self.dxcallsign = dxcallsign - - self.dxcallsign_crc = helpers.get_crc_24(self.dxcallsign) - - # check if hmac hash is provided - try: - self.log.info("[SCK] [HMAC] Looking for salt/token", local=mycallsign, remote=dxcallsign) - hmac_salt = helpers.get_hmac_salt(dxcallsign, mycallsign) - self.log.info("[SCK] [HMAC] Salt info", local=mycallsign, remote=dxcallsign, salt=hmac_salt) - except Exception: - self.log.warning("[SCK] [HMAC] No salt/token found") - hmac_salt = '' - - self.states.set("is_modem_busy", True) - self.arq_file_transfer = True - self.beacon_paused = True - - self.transmission_uuid = transmission_uuid - - # wait a moment for the case, a heartbeat is already on the way back to us - # this makes channel establishment more clean - if self.states.is_arq_session: - threading.Event().wait(2.5) - - # init arq state event - self.arq_state_event = threading.Event() - - # finally start the channel opening procedure - self.arq_open_data_channel(mycallsign) - - # if data channel is open, return true else false - if self.arq_state_event.is_set(): - # start arq transmission - self.arq_transmit(data_out, hmac_salt) - return True - - else: - self.log.debug( - "[Modem] arq_open_data_channel:", transmission_uuid=self.transmission_uuid - ) - - self.event_manager.send_custom_event( - freedata="modem-message", - arq="transmission", - status="failed", - reason="unknown", - uuid=self.transmission_uuid, - percent=self.states.arq_transmission_percent, - bytesperminute=self.states.arq_bytes_per_minute, - compression=self.arq_compression_factor, - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS), - nacks=self.frame_nack_counter, - speed_list=self.states.arq_speed_list - ) - - self.log.warning( - "[Modem] ARQ | TX | DATA [" - + str(mycallsign, "UTF-8") - + "]>>X<<[" - + str(self.dxcallsign, "UTF-8") - + "]" - ) - - # Attempt to clean up the far-side, if it received the - # open_session frame and can still hear us. - self.close_session() - - # release beacon pause - self.beacon_paused = False - - # otherwise return false - return False - - def arq_open_data_channel( - self, mycallsign - ) -> bool: - """ - Open an ARQ data channel. - - Args: - mycallsign:bytes: - - Returns: - True if the data channel was opened successfully - False if the data channel failed to open - """ - # set IRS indicator to false, because we are IRS - self.is_IRS = False - - # init a new random session id if we are not in an arq session - if not self.states.is_arq_session: - self.session_id = np.random.bytes(1) - - # Update data_channel timestamp - self.data_channel_last_received = int(time.time()) - - # check if the Modem is running in low bandwidth mode - # then set the corresponding frametype and build frame - if self.low_bandwidth_mode: - frametype = bytes([FR_TYPE.ARQ_DC_OPEN_N.value]) - self.log.debug("[Modem] Requesting low bandwidth mode") - else: - frametype = bytes([FR_TYPE.ARQ_DC_OPEN_W.value]) - self.log.debug("[Modem] Requesting high bandwidth mode") - - # build connection frame - connection_frame = self.frame_factory.build_arq_connect( - session_id=self.session_id, - destination_crc=self.dxcallsign_crc, - ) - for attempt in range(self.data_channel_max_retries): - - self.event_manager.send_custom_event( - freedata="modem-message", - arq="transmission", - status="opening", - mycallsign=mycallsign, - dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS) - ) - - self.log.info( - "[Modem] ARQ | DATA | TX | [" - + mycallsign - + "]>> <<[" - + str(self.dxcallsign, "UTF-8") - + "]", - attempt=f"{str(attempt + 1)}/{str(self.data_channel_max_retries)}", - ) - - # Let's check if we have a busy channel and if we are not in a running arq session. - if self.states.channel_busy and not self.arq_state_event.is_set() or self.states.is_codec2_traffic: - self.channel_busy_handler() - - # if channel free, enqueue frame for tx - if not self.arq_state_event.is_set(): - self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) - - # wait until timeout or event set - - random_wait_time = randrange(int(self.duration_sig1_frame * 10), - int(self.datachannel_opening_interval * 10), 1) / 10 - self.arq_state_event.wait(timeout=random_wait_time) - - if self.arq_state_event.is_set(): - return True - if not self.states.is_modem_busy: - return False - - # `data_channel_max_retries` attempts have been sent. Aborting attempt & cleaning up - return False - - def arq_received_channel_is_open(self, data_in: bytes, snr) -> None: - """ - Called if we received a data channel opener - Args: - data_in:bytes: - - """ - protocol_version = int.from_bytes(bytes(data_in[13:14]), "big") - if protocol_version == self.arq_protocol_version: - self.event_manager.send_custom_event( - freedata="modem-message", - arq="transmission", - status="opened", - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS) - ) - frametype = int.from_bytes(bytes(data_in[:1]), "big") - - if frametype == FR_TYPE.ARQ_DC_OPEN_ACK_N.value: - self.received_LOW_BANDWIDTH_MODE = True - self.mode_list = self.mode_list_low_bw - self.time_list = self.time_list_low_bw - self.log.debug("[Modem] low bandwidth mode", modes=self.mode_list) - else: - self.received_LOW_BANDWIDTH_MODE = False - self.mode_list = self.mode_list_high_bw - self.time_list = self.time_list_high_bw - self.log.debug("[Modem] high bandwidth mode", modes=self.mode_list) - - # set speed level from session opener frame delegation - self.speed_level = int.from_bytes(bytes(data_in[8:9]), "big") - self.log.debug("[Modem] speed level selected for given SNR", speed_level=self.speed_level) - - self.dxgrid = b'------' - helpers.add_to_heard_stations( - self.dxcallsign, - self.dxgrid, - "DATA", - snr, - self.modem_frequency_offset, - self.states.radio_frequency, - self.states.heard_stations - ) - - self.log.info( - "[Modem] ARQ | DATA | TX | [" - + str(self.mycallsign, "UTF-8") - + "]>>|<<[" - + str(self.dxcallsign, "UTF-8") - + "]", - snr=snr, - ) - - # as soon as we set ARQ_STATE to True, transmission starts - self.states.set("is_arq_state", True) - # also set the ARQ event - self.arq_state_event.set() - - # Update data_channel timestamp - self.data_channel_last_received = int(time.time()) - - else: - self.event_manager.send_custom_event( - freedata="modem-message", - arq="transmission", - status="failed", - reason="protocol version missmatch", - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS) - ) - self.log.warning( - "[Modem] protocol version mismatch:", - received=protocol_version, - own=self.arq_protocol_version, - ) - self.stop_transmission() - - def calculate_transfer_rate_tx( - self, tx_start_of_transmission: float, sentbytes: int, tx_buffer_length: int - ) -> list: - """ - Calculate transfer rate for transmission - Args: - tx_start_of_transmission:float: - sentbytes:int: - tx_buffer_length:int: - - Returns: List of: - bits_per_second: float, - bytes_per_minute: float, - transmission_percent: float - """ - try: - arq_transmission_percent = min( - int((sentbytes / tx_buffer_length) * 100), 100 - ) - - transmissiontime = time.time() - tx_start_of_transmission - - if sentbytes > 0: - arq_bits_per_second = int((sentbytes * 8) / transmissiontime) - bytes_per_minute = int(sentbytes / (transmissiontime / 60)) - arq_seconds_until_finish = int(((tx_buffer_length - sentbytes) / ( - bytes_per_minute * self.arq_compression_factor)) * 60) - - speed_chart = {"snr": self.burst_ack_snr, "bpm": bytes_per_minute, - "timestamp": int(time.time())} - # check if data already in list - if speed_chart not in self.states.arq_speed_list: - self.states.arq_speed_list.append(speed_chart) - - else: - arq_bits_per_second = 0 - bytes_per_minute = 0 - arq_seconds_until_finish = 0 - - except Exception as err: - self.log.error(f"[Modem] calculate_transfer_rate_tx: Exception: {err}") - arq_transmission_percent = 0.0 - arq_bits_per_second = 0 - bytes_per_minute = 0 - - self.states.set("arq_bits_per_second", arq_bits_per_second) - self.states.set("bytes_per_minute", bytes_per_minute) - self.states.set("arq_transmission_percent", arq_transmission_percent) - self.states.set("arq_compression_factor", self.arq_compression_factor) diff --git a/modem/data cemetery/deprecated_sock.py b/modem/data cemetery/deprecated_sock.py deleted file mode 100644 index 8f2fa2dd..00000000 --- a/modem/data cemetery/deprecated_sock.py +++ /dev/null @@ -1,1469 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Dec 25 21:25:14 2020 - -@author: DJ2LS - -# GET COMMANDS - # "command" : "..." - - # SET COMMANDS - # "command" : "..." - # "parameter" : " ..." - - # DATA COMMANDS - # "command" : "..." - # "type" : "..." - # "dxcallsign" : "..." - # "data" : "..." -""" -import atexit -import base64 -import queue -import socketserver -import sys -import threading -import time -import wave -import helpers -import deprecated_static -from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem, MeshParam -import structlog -from random import randrange -import ujson as json -from exceptions import NoCallsign -from queues import DATA_QUEUE_TRANSMIT, RX_BUFFER, RIGCTLD_COMMAND_QUEUE, MESH_QUEUE_TRANSMIT, MESH_SIGNALLING_TABLE - -SOCKET_QUEUE = queue.Queue() -DAEMON_QUEUE = queue.Queue() - -CONNECTED_CLIENTS = set() -CLOSE_SIGNAL = False - -TESTMODE = False - -log = structlog.get_logger("sock") - - -class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): - """ - the socket handler base class - """ - - pass - - -# noinspection PyTypeChecker -class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): - """ """ - connection_alive = False - 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 modem state as network stream - # check server port against daemon port and send corresponding data - if self.server.server_address[1] == Modem.port and not Daemon.modemstarted: - data = send_modem_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) - - while not SOCKET_QUEUE.empty(): - - try: - - data = SOCKET_QUEUE.get() - sock_data = bytes(data, "utf-8") - sock_data += b"\n" # append line limiter - - # send data to all connected clients - for client in CONNECTED_CLIENTS: - try: - client.send(sock_data) - except Exception as err: - self.log.info("[SCK] Connection lost", e=err) - - try: - self.log.warning("[SCK] removing client from sock", client=client, set=CONNECTED_CLIENTS) - CONNECTED_CLIENTS.remove(client) - except Exception as sockerr: - self.log.warning("[SCK] Err remove client from CONNECTED_CLIENTS", e=sockerr, client=client, set=CONNECTED_CLIENTS) - self.log.info("[SCK] resetting sock") - - # 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] err while sending data to sock", e=err) - - # we want to transmit scatter data only once to reduce network traffic - ModemParam.scatter = [] - # self.request.sendall(sock_data) - threading.Event().wait(0.15) - - # This will disapear because commands will come from the REST API - 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 - - if chunk == b"": - # 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") - # remove empty data - data.remove(b"") - - # iterate thorugh data list - for commands in data: - if self.server.server_address[1] == Modem.port: - self.process_modem_commands(commands) - else: - self.process_daemon_commands(commands) - - # wait some time between processing multiple commands - # this is only a first test to avoid doubled transmission - # we might improve this by only processing one command or - # doing some kind of selection to determine which commands need to be dropped - # and which one can be processed during a running transmission - threading.Event().wait(0.5) - - # 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 - - 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 - - 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() - - # 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) - except Exception as e: - self.log.warning( - "[SCK] client connection already removed from client list", - client=self.request, - e=e, - ) - - # ------------------------ Modem COMMANDS - def process_modem_commands(self, data): - """ - process modem commands - - Args: - data: - - Returns: - - """ - log = structlog.get_logger("process_modem_commands") - - # 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 Modem LISTENING STATE - if received_json["type"] == "set" and received_json["command"] == "listen": - if TESTMODE: - ThreadedTCPRequestHandler.modem_set_listen(None, received_json) - else: - self.modem_set_listen(received_json) - - # START STOP AUDIO RECORDING - if received_json["type"] == "set" and received_json["command"] == "record_audio": - if TESTMODE: - ThreadedTCPRequestHandler.modem_set_record_audio(None, received_json) - else: - self.modem_set_record_audio(received_json) - - # SET ENABLE/DISABLE RESPOND TO CALL - if received_json["type"] == "set" and received_json["command"] == "respond_to_call": - if TESTMODE: - ThreadedTCPRequestHandler.modem_set_respond_to_call(None, received_json) - else: - self.modem_set_respond_to_call(received_json) - - # SET ENABLE RESPOND TO CQ - if received_json["type"] == "set" and received_json["command"] == "respond_to_cq": - if TESTMODE: - ThreadedTCPRequestHandler.modem_set_record_audio(None, received_json) - else: - self.modem_set_record_audio(received_json) - - # SET TX AUDIO LEVEL - if received_json["type"] == "set" and received_json["command"] == "tx_audio_level": - if TESTMODE: - ThreadedTCPRequestHandler.modem_set_tx_audio_level(None, received_json) - else: - self.modem_set_tx_audio_level(received_json) - - # SET RX AUDIO LEVEL - if received_json["type"] == "set" and received_json["command"] == "rx_audio_level": - if TESTMODE: - ThreadedTCPRequestHandler.modem_set_rx_audio_level(None, received_json) - else: - self.modem_set_rx_audio_level(received_json) - - # TRANSMIT TEST FRAME - if received_json["type"] == "set" and received_json["command"] == "send_test_frame": - if TESTMODE: - ThreadedTCPRequestHandler.modem_set_send_test_frame(None, received_json) - elif Modem.modem_state in ['busy']: - log.warning( - "[SCK] Dropping command", - e="modem state", - state=Modem.modem_state, - command=received_json, - ) - else: - self.modem_set_send_test_frame(received_json) - - # TRANSMIT FEC FRAME - if received_json["type"] == "fec" and received_json["command"] == "transmit": - if TESTMODE: - ThreadedTCPRequestHandler.modem_fec_transmit(None, received_json) - else: - self.modem_fec_transmit(received_json) - - # TRANSMIT IS WRITING FRAME - if received_json["type"] == "fec" and received_json["command"] == "transmit_is_writing": - if TESTMODE: - ThreadedTCPRequestHandler.modem_fec_is_writing(None, received_json) - elif Modem.modem_state in ['busy']: - log.warning( - "[SCK] Dropping command", - e="modem state", - state=Modem.modem_state, - command=received_json, - ) - else: - self.modem_fec_is_writing(received_json) - - # CQ CQ CQ - if received_json["command"] == "cqcqcq": - if TESTMODE: - ThreadedTCPRequestHandler.modem_cqcqcq(None, received_json) - elif Modem.modem_state in ['BUSY']: - log.warning( - "[SCK] Dropping command", - e="modem state", - state=Modem.modem_state, - command=received_json, - ) - else: - self.modem_cqcqcq(received_json) - - # START_BEACON - if received_json["command"] == "start_beacon": - if TESTMODE: - ThreadedTCPRequestHandler.modem_start_beacon(None, received_json) - else: - self.modem_start_beacon(received_json) - - # STOP_BEACON - if received_json["command"] == "stop_beacon": - if TESTMODE: - ThreadedTCPRequestHandler.modem_stop_beacon(None, received_json) - else: - self.modem_stop_beacon(received_json) - - # PING - if received_json["type"] == "ping" and received_json["command"] == "ping": - - if TESTMODE: - ThreadedTCPRequestHandler.modem_ping_ping(None, received_json) - elif Modem.modem_state in ['BUSY']: - log.warning( - "[SCK] Dropping command", - e="modem state", - state=Modem.modem_state, - command=received_json, - ) - - else: - self.modem_ping_ping(received_json) - - # CONNECT - if received_json["type"] == "arq" and received_json["command"] == "connect": - if TESTMODE: - ThreadedTCPRequestHandler.modem_arq_connect(None, received_json) - elif Modem.modem_state in ['BUSY']: - log.warning( - "[SCK] Dropping command", - e="modem state", - state=Modem.modem_state, - command=received_json, - ) - else: - self.modem_arq_connect(received_json) - - # DISCONNECT - if received_json["type"] == "arq" and received_json["command"] == "disconnect": - if TESTMODE: - ThreadedTCPRequestHandler.modem_arq_disconnect(None, received_json) - else: - self.modem_arq_disconnect(received_json) - - # TRANSMIT RAW DATA - if received_json["type"] == "arq" and received_json["command"] == "send_raw": - if TESTMODE: - ThreadedTCPRequestHandler.modem_arq_send_raw(None, received_json) - elif Modem.modem_state in ['busy']: - log.warning( - "[SCK] Dropping command", - e="modem state", - state=Modem.modem_state, - command=received_json, - ) - else: - self.modem_arq_send_raw(received_json) - - # STOP TRANSMISSION - if received_json["type"] == "arq" and received_json["command"] == "stop_transmission": - if TESTMODE: - ThreadedTCPRequestHandler.modem_arq_stop_transmission(None, received_json) - else: - self.modem_arq_stop_transmission(received_json) - - # GET RX BUFFER - if received_json["type"] == "get" and received_json["command"] == "rx_buffer": - if TESTMODE: - ThreadedTCPRequestHandler.modem_get_rx_buffer(None, received_json) - else: - self.modem_get_rx_buffer(received_json) - - # DELETE RX BUFFER - if received_json["type"] == "set" and received_json["command"] == "del_rx_buffer": - if TESTMODE: - ThreadedTCPRequestHandler.modem_set_del_rx_buffer(None, received_json) - else: - self.modem_set_del_rx_buffer(received_json) - # SET FREQUENCY - if received_json["type"] == "set" and received_json["command"] == "frequency": - if TESTMODE: - ThreadedTCPRequestHandler.modem_set_frequency(None, received_json) - else: - self.modem_set_frequency(received_json) - - # SET MODE - if received_json["type"] == "set" and received_json["command"] == "mode": - if TESTMODE: - ThreadedTCPRequestHandler.modem_set_mode(None, received_json) - else: - self.modem_set_mode(received_json) - - # GET ROUTING TABLE - if received_json["type"] == "get" and received_json["command"] == "routing_table": - self.modem_get_mesh_routing_table(received_json) - - - # -------------- MESH ---------------- # - # MESH PING - if received_json["type"] == "mesh" and received_json["command"] == "ping": - self.modem_mesh_ping(received_json) - - except Exception as err: - log.error("[SCK] JSON decoding error", e=err) - - def modem_set_listen(self, received_json): - try: - Modem.listen = received_json["state"] in ['true', 'True', True, "ON", "on"] - command_response("listen", True) - - # if modem is connected, force disconnect when Modem.listen == False - if not Modem.listen and ARQ.arq_session_state not in ["disconnecting", "disconnected", "failed"]: - DATA_QUEUE_TRANSMIT.put(["DISCONNECT"]) - # set early disconnecting state so we can interrupt connection attempts - ARQ.arq_session_state = "disconnecting" - command_response("disconnect", True) - - - - except Exception as err: - command_response("listen", False) - log.warning( - "[SCK] CQ command execution error", e=err, command=received_json - ) - - def modem_set_record_audio(self, received_json): - try: - if not AudioParam.audio_record: - AudioParam.audio_record_file = wave.open(f"{int(time.time())}_audio_recording.wav", 'w') - AudioParam.audio_record_file.semodemhannels(1) - AudioParam.audio_record_file.setsampwidth(2) - AudioParam.audio_record_file.setframerate(8000) - AudioParam.audio_record = True - else: - AudioParam.audio_record = False - AudioParam.audio_record_file.close() - - command_response("respond_to_call", True) - - except Exception as err: - command_response("respond_to_call", False) - log.warning( - "[SCK] CQ command execution error", e=err, command=received_json - ) - - def modem_set_respond_to_call(self, received_json): - try: - Modem.respond_to_call = received_json["state"] in ['true', 'True', True] - command_response("respond_to_call", True) - - except Exception as err: - command_response("respond_to_call", False) - log.warning( - "[SCK] CQ command execution error", e=err, command=received_json - ) - - def modem_set_respond_to_cq(self, received_json): - try: - Modem.respond_to_cq = received_json["state"] in ['true', 'True', True] - command_response("respond_to_cq", True) - - except Exception as err: - command_response("respond_to_cq", False) - log.warning( - "[SCK] CQ command execution error", e=err, command=received_json - ) - - def modem_set_tx_audio_level(self, received_json): - try: - AudioParam.tx_audio_level = int(received_json["value"]) - command_response("tx_audio_level", True) - - except Exception as err: - command_response("tx_audio_level", False) - log.warning( - "[SCK] TX audio command execution error", - e=err, - command=received_json, - ) - - def modem_set_rx_audio_level(self, received_json): - try: - AudioParam.rx_audio_level = int(received_json["value"]) - command_response("rx_audio_level", True) - - except Exception as err: - command_response("rx_audio_level", False) - log.warning( - "[SCK] TX audio command execution error", - e=err, - command=received_json, - ) - def modem_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, - ) - - def modem_fec_transmit(self, received_json): - try: - mode = received_json["mode"] - wakeup = received_json["wakeup"] - base64data = received_json["payload"] - if len(base64data) % 4: - raise TypeError - payload = base64.b64decode(base64data) - - try: - mycallsign = received_json["mycallsign"] - mycallsign = helpers.callsign_to_bytes(mycallsign) - mycallsign = helpers.bytes_to_callsign(mycallsign) - - except Exception: - mycallsign = Station.mycallsign - - - DATA_QUEUE_TRANSMIT.put(["FEC", mode, wakeup, payload, mycallsign]) - 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, - ) - - def modem_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, - ) - - def modem_cqcqcq(self, received_json): - try: - DATA_QUEUE_TRANSMIT.put(["CQ"]) - command_response("cqcqcq", True) - - except Exception as err: - command_response("cqcqcq", False) - log.warning( - "[SCK] CQ command execution error", e=err, command=received_json - ) - - def modem_start_beacon(self, received_json): - try: - Beacon.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, - ) - - def modem_stop_beacon(self, received_json): - try: - log.warning("[SCK] Stopping beacon!") - Beacon.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, - ) - - - def modem_mesh_ping(self, received_json): - # send ping frame and wait for ACK - try: - dxcallsign = received_json["dxcallsign"] - 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) - - # check if specific callsign is set with different SSID than the Modem is initialized - try: - mycallsign = received_json["mycallsign"] - mycallsign = helpers.callsign_to_bytes(mycallsign) - mycallsign = helpers.bytes_to_callsign(mycallsign) - - except Exception: - mycallsign = Station.mycallsign - - MESH_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 - ) - - def modem_ping_ping(self, received_json): - # send ping frame and wait for ACK - - try: - dxcallsign = received_json["dxcallsign"] - 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) - - # check if specific callsign is set with different SSID than the Modem is initialized - try: - mycallsign = received_json["mycallsign"] - mycallsign = helpers.callsign_to_bytes(mycallsign) - mycallsign = helpers.bytes_to_callsign(mycallsign) - - except Exception: - mycallsign = Station.mycallsign - - 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 - ) - - def modem_arq_connect(self, received_json): - - # pause our beacon first - Beacon.beacon_pause = True - - # check for connection attempts key - try: - attempts = int(received_json["attempts"]) - except Exception: - # 15 == self.session_connect_max_retries - attempts = 15 - - dxcallsign = received_json["dxcallsign"] - - # check if specific callsign is set with different SSID than the Modem is initialized - try: - mycallsign = received_json["mycallsign"] - mycallsign = helpers.callsign_to_bytes(mycallsign) - mycallsign = helpers.bytes_to_callsign(mycallsign) - - except Exception: - mycallsign = Station.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 ARQ.arq_session_state not in ["disconnected", "failed"]: - command_response("connect", False) - log.warning( - "[SCK] Connect command execution error", - e=f"already connected to station:{Station.dxcallsign}", - command=received_json, - ) - else: - - # finally check again if we are disconnected or failed - - # try connecting - try: - - DATA_QUEUE_TRANSMIT.put(["CONNECT", mycallsign, dxcallsign, attempts]) - command_response("connect", True) - except Exception as err: - command_response("connect", False) - log.warning( - "[SCK] Connect command execution error", - e=err, - command=received_json, - ) - # allow beacon transmission again - Beacon.beacon_pause = False - - # allow beacon transmission again - Beacon.beacon_pause = False - - def modem_arq_disconnect(self, received_json): - try: - if ARQ.arq_session_state not in ["disconnecting", "disconnected", "failed"]: - DATA_QUEUE_TRANSMIT.put(["DISCONNECT"]) - - # set early disconnecting state so we can interrupt connection attempts - ARQ.arq_session_state = "disconnecting" - command_response("disconnect", True) - else: - command_response("disconnect", False) - - except Exception as err: - command_response("disconnect", False) - log.warning( - "[SCK] Disconnect command execution error", - e=err, - command=received_json, - ) - - def modem_arq_send_raw(self, received_json): - Beacon.beacon_pause = True - - # wait some random time - helpers.wait(randrange(5, 25, 5) / 10.0) - - # TODO carefully test this - # avoid sending data while we are receiving codec2 signalling data - interrupt_time = time.time() + 5 - while ModemParam.is_codec2_traffic and time.time() < interrupt_time: - threading.Event().wait(0.01) - - # we need to warn if already in arq state - if ARQ.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, - ) - - try: - if not ARQ.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) - - command_response("send_raw", True) - else: - dxcallsign = Station.dxcallsign - Station.dxcallsign_crc = helpers.get_crc_24(Station.dxcallsign) - - base64data = received_json["parameter"][0]["data"] - - # check if specific callsign is set with different SSID than the Modem is initialized - try: - mycallsign = received_json["parameter"][0]["mycallsign"] - mycallsign = helpers.callsign_to_bytes(mycallsign) - mycallsign = helpers.bytes_to_callsign(mycallsign) - - except Exception: - mycallsign = Station.mycallsign - - # check for connection attempts key - try: - attempts = int(received_json["parameter"][0]["attempts"]) - - except Exception: - attempts = 10 - - # check if transmission uuid provided else set no-uuid - try: - arq_uuid = received_json["uuid"] - except Exception: - arq_uuid = "no-uuid" - - if len(base64data) % 4: - raise TypeError - - binarydata = base64.b64decode(base64data) - # check if hmac hash is provided - try: - log.info("[SCK] [HMAC] Looking for salt/token", local=mycallsign, remote=dxcallsign) - hmac_salt = helpers.get_hmac_salt(dxcallsign, mycallsign) - log.info("[SCK] [HMAC] Salt info", local=mycallsign, remote=dxcallsign, salt=hmac_salt) - except Exception: - log.warning("[SCK] [HMAC] No salt/token found") - hmac_salt = '' - DATA_QUEUE_TRANSMIT.put( - ["ARQ_RAW", binarydata, arq_uuid, mycallsign, dxcallsign, attempts, hmac_salt] - ) - - except Exception as err: - command_response("send_raw", False) - log.warning( - "[SCK] Send raw command execution error", - e=err, - command=received_json, - ) - - def modem_arq_stop_transmission(self, received_json): - try: - if Modem.modem_state == "BUSY" or ARQ.arq_state: - DATA_QUEUE_TRANSMIT.put(["STOP"]) - log.warning("[SCK] Stopping transmission!") - Modem.modem_state = "IDLE" - ARQ.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 modem_get_mesh_routing_table(self, received_json): - try: - output = { - "command": "routing_table", - "routes": [], - } - - for _, route in enumerate(MeshParam.routing_table): - if MeshParam.routing_table[_][0].hex() == helpers.get_crc_24(b"direct").hex(): - router = "direct" - else: - router = MeshParam.routing_table[_][0].hex() - output["routes"].append( - { - "dxcall": MeshParam.routing_table[_][0].hex(), - "router": router, - "hops": MeshParam.routing_table[_][2], - "snr": MeshParam.routing_table[_][3], - "score": MeshParam.routing_table[_][4], - "timestamp": MeshParam.routing_table[_][5], - } - ) - - - jsondata = json.dumps(output) - # self.request.sendall(bytes(jsondata, encoding)) - SOCKET_QUEUE.put(jsondata) - command_response("routing_table", True) - - except Exception as err: - command_response("routing_table", False) - log.warning( - "[SCK] Send RX buffer command execution error", - e=err, - command=received_json, - ) - - def modem_get_rx_buffer(self, received_json): - try: - if not RX_BUFFER.empty(): - # TODO REMOVE DEPRECATED MESSAGES - #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) - - - # REQUEST REQUEST RX BUFFER AGAIN - # NEW BEHAVIOUR IS, PUSHING DATA TO NETWORK LIKE WE RECEIVED IT - # RX_BUFFER[0] = transmission uuid - # RX_BUFFER[1] = timestamp - # RX_BUFFER[2] = dxcallsign - # RX_BUFFER[3] = dxgrid - # RX_BUFFER[4] = data - # RX_BUFFER[5] = hmac signed - # RX_BUFFER[6] = compression factor - # RX_BUFFER[7] = bytes per minute - # RX_BUFFER[8] = duration - # RX_BUFFER[9] = self.frame_nack_counter - # RX_BUFFER[10] = speed list stats - for _buffer_length in range(RX_BUFFER.qsize()): - output = { - "freedata" : "modem-message", - "arq" : "transmission", - "status" : "received", - "uuid" : RX_BUFFER.queue[_buffer_length][0], - "percent" : 100, - "bytesperminute" : RX_BUFFER.queue[_buffer_length][7], - "compression" : RX_BUFFER.queue[_buffer_length][6], - "timestamp" : RX_BUFFER.queue[_buffer_length][1], - "finished" : 0, - "mycallsign" : str(Station.mycallsign, "UTF-8"), - "dxcallsign" : str(RX_BUFFER.queue[_buffer_length][2], "utf-8"), - "dxgrid" : str(RX_BUFFER.queue[_buffer_length][3], "utf-8"), - "data" : RX_BUFFER.queue[_buffer_length][4], - "irs" : RX_BUFFER.queue[_buffer_length][5], - "hmac_signed" : "False", - "duration" : RX_BUFFER.queue[_buffer_length][8], - "nacks" : RX_BUFFER.queue[_buffer_length][9], - "speed_list" : RX_BUFFER.queue[_buffer_length][10] - } - - jsondata = json.dumps(output) - SOCKET_QUEUE.put(jsondata) - print(jsondata) - - command_response("rx_buffer", True) - - - - - except Exception as err: - command_response("rx_buffer", False) - log.warning( - "[SCK] Send RX buffer command execution error", - e=err, - command=received_json, - ) - - def modem_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, - ) - - def modem_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, - ) - - def modem_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, - ) - - # ------------------------ DAEMON COMMANDS - def process_daemon_commands(self, data): - """ - process daemon commands - - Args: - data: - - Returns: - - """ - log = structlog.get_logger("process_daemon_commands") - - # 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) - - if received_json["type"] == "set" and received_json["command"] == "mygrid": - self.daemon_set_mygrid(received_json) - - if ( - received_json["type"] == "set" - and received_json["command"] == "start_modem" - # and not Daemon.modemstarted - ): - self.daemon_start_modem(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_modem": - self.daemon_stop_modem(received_json) - - if received_json["type"] == "set" and received_json["command"] == "start_rigctld" and not Daemon.rigctldstarted: - self.daemon_start_rigctld(received_json) - - if received_json["type"] == "set" and received_json["command"] == "stop_rigctld": - self.daemon_stop_rigctld(received_json) - - def daemon_set_mycallsign(self, received_json): - try: - callsign = received_json["parameter"] - - if bytes(callsign, "utf-8") == b"": - self.request.sendall(b"INVALID CALLSIGN") - log.warning( - "[SCK] SET MYCALL FAILED", - call=Station.mycallsign, - crc=Station.mycallsign_crc.hex(), - ) - else: - Station.mycallsign = bytes(callsign, "utf-8") - Station.mycallsign_crc = helpers.get_crc_24(Station.mycallsign) - - command_response("mycallsign", True) - log.info( - "[SCK] SET MYCALL", - call=Station.mycallsign, - crc=Station.mycallsign_crc.hex(), - ) - except Exception as err: - command_response("mycallsign", False) - log.warning("[SCK] command execution error", e=err, command=received_json) - - 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: - Station.mygrid = bytes(mygrid, "utf-8") - log.info("[SCK] SET MYGRID", grid=Station.mygrid) - command_response("mygrid", True) - except Exception as err: - command_response("mygrid", False) - log.warning("[SCK] command execution error", e=err, command=received_json) - - def daemon_start_modem(self, received_json): - try: - startparam = received_json["parameter"][0] - - 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("0", startparam, "tx_audio_level")) - rx_audio_level = str(helpers.return_key_from_object("0", startparam, "rx_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")) - tx_delay = str(helpers.return_key_from_object("0", startparam, "tx_delay")) - tci_ip = str(helpers.return_key_from_object("127.0.0.1", startparam, "tci_ip")) - tci_port = str(helpers.return_key_from_object("50001", startparam, "tci_port")) - enable_mesh = str(helpers.return_key_from_object("False", startparam, "enable_mesh")) - try: - # convert ssid list to python list - 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: - ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - - # print some debugging parameters - for item in startparam: - log.debug( - f"[SCK] Modem Startup Config : {item}", - value=startparam[item], - ) - - DAEMON_QUEUE.put( - [ - "STARTModem", - 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, - enable_explorer, - ssid_list, - enable_auto_tune, - enable_stats, - tx_delay, - tci_ip, - tci_port, - enable_mesh, - rx_audio_level, - ] - ) - command_response("start_modem", True) - - except Exception as err: - command_response("start_modem", False) - log.warning("[SCK] command execution error", e=err, command=received_json) - - def daemon_stop_modem(self, received_json): - try: - log.warning("[SCK] Stopping Modem") - Daemon.modemstarted = False - # we need to run this twice, otherwise process won't be stopped - Daemon.modemprocess.kill() - threading.Event().wait(0.3) - Daemon.modemprocess.kill() - # unregister process from atexit to avoid process zombies - atexit.unregister(Daemon.modemprocess.kill) - - command_response("stop_modem", True) - except Exception as err: - command_response("stop_modem", 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: - command_response("test_hamlib", False) - log.warning("[SCK] command execution error", e=err, command=received_json) - - def daemon_start_rigctld(self, received_json): - """ - hamlib_deviceid: settings.hamlib_deviceid, - hamlib_deviceport: settings.hamlib_deviceport, - hamlib_stop_bits: settings.hamlib_stop_bits, - hamlib_data_bits: settings.hamlib_data_bits, - hamlib_handshake: settings.hamlib_handshake, - hamlib_serialspeed: settings.hamlib_serialspeed, - hamlib_dtrstate: settings.hamlib_dtrstate, - hamlib_pttprotocol: settings.hamlib_pttprotocol, - hamlib_ptt_port: settings.hamlib_ptt_port, - hamlib_dcd: settings.hamlib_dcd, - hamlbib_serialspeed_ptt: settings.hamlib_serialspeed, - hamlib_rigctld_port: settings.hamlib_rigctld_port, - hamlib_rigctld_ip: settings.hamlib_rigctld_ip, - hamlib_rigctld_path: settings.hamlib_rigctld_path, - hamlib_rigctld_server_port: settings.hamlib_rigctld_server_port, - hamlib_rigctld_custom_args: settings.hamlib_rigctld_custom_args - """ - try: - - hamlib_deviceid = str(received_json["parameter"][0]["hamlib_deviceid"]) - hamlib_deviceport = str(received_json["parameter"][0]["hamlib_deviceport"]) - hamlib_stop_bits = str(received_json["parameter"][0]["hamlib_stop_bits"]) - hamlib_data_bits = str(received_json["parameter"][0]["hamlib_data_bits"]) - hamlib_handshake = str(received_json["parameter"][0]["hamlib_handshake"]) - hamlib_serialspeed = str(received_json["parameter"][0]["hamlib_serialspeed"]) - hamlib_dtrstate = str(received_json["parameter"][0]["hamlib_dtrstate"]) - hamlib_pttprotocol = str(received_json["parameter"][0]["hamlib_pttprotocol"]) - hamlib_ptt_port = str(received_json["parameter"][0]["hamlib_ptt_port"]) - hamlib_dcd = str(received_json["parameter"][0]["hamlib_dcd"]) - hamlbib_serialspeed_ptt = str(received_json["parameter"][0]["hamlib_serialspeed"]) - hamlib_rigctld_port = str(received_json["parameter"][0]["hamlib_rigctld_port"]) - hamlib_rigctld_ip = str(received_json["parameter"][0]["hamlib_rigctld_ip"]) - hamlib_rigctld_path = str(received_json["parameter"][0]["hamlib_rigctld_path"]) - hamlib_rigctld_server_port = str(received_json["parameter"][0]["hamlib_rigctld_server_port"]) - hamlib_rigctld_custom_args = str(received_json["parameter"][0]["hamlib_rigctld_custom_args"]) - - DAEMON_QUEUE.put( - [ - "START_RIGCTLD", - hamlib_deviceid, - hamlib_deviceport, - hamlib_stop_bits, - hamlib_data_bits, - hamlib_handshake, - hamlib_serialspeed, - hamlib_dtrstate, - hamlib_pttprotocol, - hamlib_ptt_port, - hamlib_dcd, - hamlbib_serialspeed_ptt, - hamlib_rigctld_port, - hamlib_rigctld_ip, - hamlib_rigctld_path, - hamlib_rigctld_server_port, - hamlib_rigctld_custom_args - ] - ) - command_response("start_rigctld", True) - except Exception as err: - command_response("start_rigctld", False) - log.warning("[SCK] command execution error", e=err, command=received_json) - - - def daemon_stop_rigctld(self, received_json): - - try: - log.warning("[SCK] Stopping rigctld") - - if Daemon.rigctldstarted: - Daemon.rigctldprocess.kill() - # unregister process from atexit to avoid process zombies - atexit.unregister(Daemon.rigctldprocess.kill) - - Daemon.rigctldstarted = False - command_response("stop_rigctld", True) - except Exception as err: - command_response("stop_modem", False) - log.warning("[SCK] command execution error", e=err, command=received_json) - - - - -def send_daemon_state(): - """ - send the daemon state to network - """ - log = structlog.get_logger("send_daemon_state") - - # we need to do some process checking for providing the correct state - # at least we are checking the returncode of rigctld - # None state means, the process is still running - try: - retcode_rigctld = Daemon.rigctldprocess - if retcode_rigctld in [None, "None"]: - Daemon.rigctldstarted = False - # This is a blocking code .... - # output, errs = Daemon.rigctldprocess.communicate() - # print(f"rigctld out: {output}") - # print(f"rigctld err: {errs}") - else: - # print(f"rigctld closed with code: {retcode_rigctld}") - Daemon.rigctldstarted = True - - - retcode_modem = Daemon.modemprocess - if retcode_modem in [None, "None"]: - Daemon.modemstarted = False - # This is a blocking code .... - # output, errs = Daemon.modemprocess.communicate() - # print(f"modem out: {output}") - # print(f"modem err: {errs}") - else: - # print(f"modem closed with code: {retcode_modem}") - Daemon.modemstarted = True - - except Exception as err: - log.warning("[DMN] error", e=err) - - try: - python_version = f"{str(sys.version_info[0])}.{str(sys.version_info[1])}" - - output = { - "command": "daemon_state", - "daemon_state": [], - "rigctld_state": [], - "python_version": str(python_version), - "input_devices": AudioParam.audio_input_devices, - "output_devices": AudioParam.audio_output_devices, - "serial_devices": Daemon.serial_devices, - # 'cpu': str(psutil.cpu_percent()), - # 'ram': str(psutil.virtual_memory().percent), - "version": Modem.version, - } - - if Daemon.modemstarted: - output["daemon_state"].append({"status": "running"}) - else: - output["daemon_state"].append({"status": "stopped"}) - - if Daemon.rigctldstarted: - output["rigctld_state"].append({"status": "running"}) - else: - output["rigctld_state"].append({"status": "stopped"}) - - return json.dumps(output) - except Exception as err: - log.warning("[SCK] error", e=err) - return None - - -def send_modem_state(): - """ - send the modem state to network - """ - encoding = "utf-8" - output = { - "command": "modem_state", - "ptt_state": str(HamlibParam.ptt_state), - "modem_state": str(Modem.modem_state), - "arq_state": str(ARQ.arq_state), - "arq_session": str(ARQ.arq_session), - "arq_session_state": str(ARQ.arq_session_state), - "audio_dbfs": str(AudioParam.audio_dbfs), - "snr": str(ModemParam.snr), - "frequency": str(HamlibParam.hamlib_frequency), - "rf_level": str(HamlibParam.hamlib_rf), - "strength": str(HamlibParam.hamlib_strength), - "alc": str(HamlibParam.alc), - "tx_audio_level": str(AudioParam.tx_audio_level), - "rx_audio_level": str(AudioParam.tx_audio_level), - "audio_auto_tune": str(AudioParam.audio_auto_tune), - "speed_level": str(ARQ.arq_speed_level), - "mode": str(HamlibParam.hamlib_mode), - "bandwidth": str(HamlibParam.hamlib_bandwidth), - "fft": str(AudioParam.fft), - "channel_busy": str(ModemParam.channel_busy), - "channel_busy_slot": str(ModemParam.channel_busy_slot), - "is_codec2_traffic": str(ModemParam.is_codec2_traffic), - "scatter": ModemParam.scatter, - "rx_buffer_length": str(RX_BUFFER.qsize()), - "rx_msg_buffer_length": str(len(ARQ.rx_msg_buffer)), - "arq_bytes_per_minute": str(ARQ.bytes_per_minute), - "arq_bytes_per_minute_burst": str(ARQ.bytes_per_minute_burst), - "arq_seconds_until_finish": str(ARQ.arq_seconds_until_finish), - "arq_seconds_until_timeout": str(ARQ.arq_seconds_until_timeout), - "arq_compression_factor": str(ARQ.arq_compression_factor), - "arq_transmission_percent": str(ARQ.arq_transmission_percent), - "speed_list": ARQ.speed_list, - "total_bytes": str(ARQ.total_bytes), - "beacon_state": str(Beacon.beacon_state), - "stations": [], - "routing_table": [], - "mesh_signalling_table" : [], - "mycallsign": str(Station.mycallsign, encoding), - "mygrid": str(Station.mygrid, encoding), - "dxcallsign": str(Station.dxcallsign, encoding), - "dxgrid": str(Station.dxgrid, encoding), - "hamlib_status": HamlibParam.hamlib_status, - "listen": str(Modem.listen), - "audio_recording": str(AudioParam.audio_record), - - } - - # add heard stations to heard stations object - for heard in Modem.heard_stations: - output["stations"].append( - { - "dxcallsign": str(heard[0], encoding), - "dxgrid": str(heard[1], encoding), - "timestamp": heard[2], - "datatype": heard[3], - "snr": heard[4], - "offset": heard[5], - "frequency": heard[6], - } - ) - - for _, route in enumerate(MeshParam.routing_table): - if MeshParam.routing_table[_][1].hex() == helpers.get_crc_24(b"direct").hex(): - router = "direct" - else: - router = MeshParam.routing_table[_][1].hex() - output["routing_table"].append( - { - "dxcall": MeshParam.routing_table[_][0].hex(), - "router": router, - "hops": MeshParam.routing_table[_][2], - "snr": MeshParam.routing_table[_][3], - "score": MeshParam.routing_table[_][4], - "timestamp": MeshParam.routing_table[_][5], - } - ) - - for _, entry in enumerate(MESH_SIGNALLING_TABLE): - - output["mesh_signalling_table"].append( - { - "timestamp": MESH_SIGNALLING_TABLE[_][0], - "destination": MESH_SIGNALLING_TABLE[_][1], - "origin": MESH_SIGNALLING_TABLE[_][2], - "frametype": MESH_SIGNALLING_TABLE[_][3], - "payload": MESH_SIGNALLING_TABLE[_][4], - "attempt": MESH_SIGNALLING_TABLE[_][5], - "status": MESH_SIGNALLING_TABLE[_][6], - - } - ) - - try: - json_out = json.dumps(output) - return json_out - - except Exception as e: - log.warning("[SCK] error while json conversion for modem state", e=e, data=output) - - - -def command_response(command, 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) - - -def try_except(string): - try: - return string - except Exception: - return False diff --git a/modem/data cemetery/test/001_highsnr_stdio_audio/test_virtual3.sh b/modem/data cemetery/test/001_highsnr_stdio_audio/test_virtual3.sh deleted file mode 100755 index e49f978f..00000000 --- a/modem/data cemetery/test/001_highsnr_stdio_audio/test_virtual3.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -x -# Run a test using the virtual sound cards, Python audio I/O - -function check_alsa_loopback { - lsmod | grep snd_aloop >> /dev/null - if [ $? -eq 1 ]; then - echo "ALSA loopback device not present. Please install with:" - echo - echo " sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT2" - exit 1 - fi -} - -check_alsa_loopback - -# make sure all child processes are killed when we exit -trap 'jobs -p | xargs -r kill' EXIT - -python3 util_callback_rx.py --mode datac13 --frames 2 --bursts 3 --audiodev -2 --debug & -rx_pid=$! -#sleep 1 -python3 util_tx.py --mode datac13 --frames 2 --bursts 3 --audiodev -2 -wait ${rx_pid} diff --git a/modem/data cemetery/test/002_highsnr_ping_pong/README.md b/modem/data cemetery/test/002_highsnr_ping_pong/README.md deleted file mode 100644 index ea9a2b55..00000000 --- a/modem/data cemetery/test/002_highsnr_ping_pong/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# FreeDV-JATE [Just Another TNC Experiment] - -## 002_HIGHSNR_PING_PONG - -### INSTALL TEST SUITE - -#### Install prerequierements - -``` -sudo apt update -sudo apt upgrade -sudo apt install git cmake build-essential python3-pip portaudio19-dev python3-pyaudio -pip3 install crcengine -pip3 install threading -``` - -Go into a directory of your choice -Run the following commands --> They will download and compile the latest codec2 ( dr-packet ) files and LPCNet as well into the directory of your choice - -``` -wget https://raw.githubusercontent.com/DJ2LS/FreeDV-JATE/002_HIGHSNR_PING_PONG/install_test_suite.sh -chmod +x install_test_suite.sh -./install_test_suite.sh -``` - -### PARAMETERS - -| parameter | description | side | -| ---------------- | ------------------------------------------ | ----------------------- | -| - -txmode 12 | set the mode for FreeDV ( 10,11,12,14 ) | Terminal 1 & Terminal 2 | -| - -rxmode 14 | set the mode for FreeDV ( 10,11,12,14 ) | Terminal 1 & Terminal 2 | -| - -frames 1 | set the number of frames per burst | Terminal 1 | -| - -bursts 1 | set the number of bursts | Terminal 1 | -| - -audioinput 2 | set the audio device | Terminal 1 & Terminal 2 | -| - -audiooutput 1 | set the audio device | Terminal 1 & Terminal 2 | -| - -debug | if used, print additional debugging output | Terminal 1 & Terminal 2 | - -### AUDIO TESTS VIA VIRTUAL AUDIO DEVICE - -#### Create audio sinkhole and subdevices - -Note: This command needs to be run again after every reboot - -``` -sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT2 -``` - -check if devices have been created - - aplay -l - -Output should be like this: - -``` - Karte 0: Intel [HDA Intel], Gerät 0: Generic Analog [Generic Analog] - Sub-Geräte: 1/1 - Sub-Gerät #0: subdevice #0 - Karte 1: CHAT1 [Loopback], Gerät 0: Loopback PCM [Loopback PCM] - Sub-Geräte: 1/1 - Sub-Gerät #0: subdevice #0 - Karte 1: CHAT1 [Loopback], Gerät 1: Loopback PCM [Loopback PCM] - Sub-Geräte: 1/1 - Sub-Gerät #0: subdevice #0 - Karte 2: CHAT2 [Loopback], Gerät 0: Loopback PCM [Loopback PCM] - Sub-Geräte: 1/1 - Sub-Gerät #0: subdevice #0 - Karte 2: CHAT2 [Loopback], Gerät 1: Loopback PCM [Loopback PCM] - Sub-Geräte: 1/1 - Sub-Gerät #0: subdevice #0 -``` - -### Run tests: - -#### Terminal 1: Ping - -``` -python3 PING.py --txmode 12 --rxmode 14 --audioinput 2 --audiooutput 2 --frames 1 --bursts 2 -``` - -Output - -``` -BURSTS: 2 FRAMES: 1 ------------------------------------------------------------------ -TX | PING | BURST [1/2] FRAME [1/1] -RX | PONG | BURST [1/2] FRAME [1/1] ------------------------------------------------------------------ -TX | PING | BURST [2/2] FRAME [1/1] -RX | PONG | BURST [2/2] FRAME [1/1] -``` - -#### Terminal 2: Pong - -``` -python3 PONG.py --txmode 14 --rxmode 12 --audioinput 2 --audiooutput 2 -``` - -Output - -``` -RX | BURST [1/2] FRAME [1/1] >>> SENDING PONG -RX | BURST [2/2] FRAME [1/1] >>> SENDING PONG -``` diff --git a/modem/data cemetery/test/README.md b/modem/data cemetery/test/README.md deleted file mode 100644 index e047b078..00000000 --- a/modem/data cemetery/test/README.md +++ /dev/null @@ -1,165 +0,0 @@ -# Unit Test Menu - -The following `CTest` tests cover some TNC functionality and the interface to codec2: - -1. Name: `audio_buffer` - Tests the thread safety of the audio buffer routines. -1. Name: `resampler` - Tests FreeDATA audio resampling from 48KHz to 8KHz. -1. Name: `tnc_state_machine` - Tests TNC transitions between states. This needs to be expanded. -1. Name: `tnc_irs_iss` - Tests TNC modem queue interaction. This needs to be expanded. -1. Name: `helper_routines` - Tests various helper routines. -1. Name: `py_highsnr_stdio_P_P_multi` - Tests a high signal-to-noise ratio (good quality) audio path using multiple codecs. (Pure python.) -1. Name: `py_highsnr_stdio_P_P_datacx` - Tests a high signal-to-noise ratio audio path using multiple individual codecs. -1. Name: `py_highsnr_stdio_P_C_datacx` - Tests a high signal-to-noise ratio audio path using multiple individual codecs. -1. Name: `py_highsnr_stdio_C_P_datacx` - Tests a high signal-to-noise ratio audio path using multiple individual codecs. -1. Name: `highsnr_stdio_P_C_single` - Tests compatibility with FreeDATA's transmit and freedv's raw data receive. -1. Name: `highsnr_stdio_C_P_single` - Tests compatibility with freedv's raw data transmit and FreeDATA's receive. -1. Name: `highsnr_stdio_P_P_single` - Tests a high signal-to-noise ratio audio path using multiple codecs. (Requires POSIX system.) -1. Name: `highsnr_stdio_P_P_multi` - Tests a high signal-to-noise ratio audio path using multiple codecs. (Requires POSIX system.) - -The following tests can not currently be run with GitHub's pipeline as they require the ALSA dummy device -kernel module to be installed. They also do not perform reliably. These tests are slowly being -replaced with equivalent pipeline-compatible tests. - -1. Name: `highsnr_virtual1_P_P_single_alsa` - Tests a high signal-to-noise ratio audio path using a single codec directly over an ALSA dummy device. -1. Name: `highsnr_virtual2_P_P_single` - Tests a high signal-to-noise ratio audio path using a single codec over an ALSA dummy device. - **Not functional** due to an incompatibility between the two scripts in the way they determine audio devices. -1. Name: `highsnr_virtual3_P_P_multi` - Tests a high signal-to-noise ratio audio path using multiple codecs over an ALSA dummy device. -1. Name: `highsnr_virtual4_P_P_single_callback` - **Not functional** due to an incompatibility between the two scripts in the way they determine audio devices. -1. Name: `highsnr_virtual4_P_P_single_callback_outside` - **Not functional** due to an incompatibility between the two scripts in the way they determine audio devices. -1. Name: `highsnr_virtual5_P_P_multi_callback` -1. Name: `highsnr_virtual5_P_P_multi_callback_outside` - -# Instructions - -1. Install: - ``` - cd FreeDATA - mkdir build - cd build - cmake -DCODEC2_BUILD_DIR=$HOME/codec2/build_linux .. - ``` -2. List available tests: - - ``` - ctest -N - Test project /home/david/FreeDATA/build - Test #1: 000_audio_tests - Test #2: 001_highsnr_stdio_audio - - Total Tests: 2 - ``` - -3. Run tests: - ``` - ctest --output-on-failure - ``` - or, to include only GitHub-compatible tests: - ``` - GITHUB_RUN_ID=0 ctest --output-on-failure - ``` -4. Run tests verbosely: - ``` - ctest -V - ``` - -# 001_HIGHSNR_STDIO_AUDIO TEST SUITE - -1. Install - ``` - sudo apt update - sudo apt upgrade - sudo apt install git cmake build-essential python3-pip portaudio19-dev python3-pyaudio - pip3 install crcengine - pip3 install threading - ``` -1. Install codec2, and set up the `libcodec2.so` shared library path, for example - ``` - export LD_LIBRARY_PATH=${HOME}/codec2/build_linux/src - ``` - -## STDIO tests - -Pipes are used to move audio samples from the Tx to Rx: - -``` -python3 util_tx.py --mode datac1 --delay 500 --frames 2 --bursts 1 | python3 util_rx.py --mode datac1 --frames 2 --bursts 1 -``` - -## Moderate signal-to-noise ratio (SNR) - -Tests need to be written that test a low SNR data path so that the TNC performance when packets are lost can be evaluated. - -## AUDIO test via virtual audio devices - -### Important: - -The virtual audio devices are great for testing, but they are also a little tricky to handle. So there's a high chance, the tests will fail, if you are running them via virtual audio devices. You should run the tests several times, while keeping this in mind. Most time the ctest is working even if it is failing. - -1. Create virtual audio devices. Note: This command needs to be run again after every reboot - - ``` - sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT2 - ``` - -1. Check if devices have been created - - ``` - aplay -l - - Karte 0: Intel [HDA Intel], Gerät 0: Generic Analog [Generic Analog] - Sub-Geräte: 1/1 - Sub-Gerät #0: subdevice #0 - Karte 1: CHAT1 [Loopback], Gerät 0: Loopback PCM [Loopback PCM] - Sub-Geräte: 1/1 - Sub-Gerät #0: subdevice #0 - Karte 1: CHAT1 [Loopback], Gerät 1: Loopback PCM [Loopback PCM] - Sub-Geräte: 1/1 - Sub-Gerät #0: subdevice #0 - Karte 2: CHAT2 [Loopback], Gerät 0: Loopback PCM [Loopback PCM] - Sub-Geräte: 1/1 - Sub-Gerät #0: subdevice #0 - Karte 2: CHAT2 [Loopback], Gerät 1: Loopback PCM [Loopback PCM] - Sub-Geräte: 1/1 - Sub-Gerät #0: subdevice #0 - ``` - -1. Determine the audio device number you would like to use: - - ``` - python3 util_rx.py --list - - audiodev: 0 HDA Intel PCH: ALC269VC Analog (hw:0,0) - audiodev: 1 HDA Intel PCH: HDMI 0 (hw:0,3) - audiodev: 2 HDA Intel PCH: HDMI 1 (hw:0,7) - audiodev: 3 HDA Intel PCH: HDMI 2 (hw:0,8) - audiodev: 4 Loopback: PCM (hw:1,0) - audiodev: 5 Loopback: PCM (hw:1,1) - audiodev: 6 Loopback: PCM (hw:2,0) - audiodev: 7 Loopback: PCM (hw:2,1) - ``` - - In this case we choose audiodev 4 for the RX and 5 for the Tx. - -1. Start the Rx first, then Tx in separate consoles: - ``` - python3 util_rx.py --mode datac0 --frames 2 --bursts 1 --audiodev 4 --debug - python3 util_tx.py --mode datac0 --frames 2 --bursts 1 --audiodev 5 - ``` diff --git a/modem/data cemetery/test/hamlib-test.py b/modem/data cemetery/test/hamlib-test.py deleted file mode 100644 index ad68f7d5..00000000 --- a/modem/data cemetery/test/hamlib-test.py +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - - -import ctypes -from ctypes import * -import pathlib - -from enum import Enum - - -class DEBUGLEVEL(Enum): - RIG_DEBUG_NONE = 0 - RIG_DEBUG_BUG = 1 - RIG_DEBUG_ERR = 2 - RIG_DEBUG_WARN = 3 - RIG_DEBUG_VERBOSE = 4 - RIG_DEBUG_TRACE = 5 - RIG_DEBUG_CACHE = 6 - - -class RETCODE(Enum): - RIG_OK = 0 - RIG_EINVAL = 1 - RIG_ECONF = 2 - RIG_ENOMEM = 3 - RIG_ENIMPL = 4 - RIG_ETIMEOUT = 5 - RIG_EIO = 6 - RIG_EINTERNAL = 7 - RIG_EPROTO = 8 - RIG_ERJCTED = 9 - RIG_ETRUNC = 10 - RIG_ENAVAIL = 11 - RIG_ENTARGET = 12 - RIG_BUSERROR = 13 - RIG_BUSBUSY = 14 - RIG_EARG = 15 - RIG_EVFO = 16 - RIG_EDOM = 17 - -libname = pathlib.Path("../modem/lib/hamlib/linux/libhamlib.so") -hamlib = ctypes.CDLL(libname) - -class SERIAL(ctypes.Structure): - _fields_ = [ - ("data_bits", ctypes.c_int), - ("stop_bits", ctypes.c_int), - ("rate", ctypes.c_int), - ("parity", ctypes.c_int), - ("handshake", ctypes.c_void_p), - ] - - -class PARM(ctypes.Structure): - _fields_ = [ - ("serial", SERIAL), - ] - - -class TYPE(ctypes.Structure): - _fields_ = [ - ("rig", ctypes.c_void_p), - ] - - -class MYPORT(ctypes.Structure): - _fields_ = [ - ("pathname", ctypes.c_char), - ("model", ctypes.c_int), - ("parm", PARM), - ("type", TYPE), - - ] - - -hamlib.rig_set_debug(9) # 6 -myrig_model = 3085 # 3085 = ICOM 6 = DUMMY - -myport = MYPORT() -myport.parm.serial.data_bits = 7 -myport.parm.serial.stop_bits = 2 -myport.parm.serial.rate = 9600 - -rig = hamlib.rig_init(myrig_model) - -retcode = hamlib.rig_set_parm(rig, 'stop_bits', 5) -print(retcode) - -''' -parameter = create_string_buffer(16) -retcode = hamlib.rig_get_parm(rig, 0, parameter) -print(retcode) -print(bytes(parameter)) -''' - - -# attempt to access global vars. Maybe we can access structures as well? -# https://github.com/Hamlib/Hamlib/blob/f5b229f9dc4b4364d2f40e0b0b415e92c9a371ce/src/rig.c#L95 -hamlib_version = ctypes.cast(hamlib.hamlib_version, ctypes.POINTER(ctypes.c_char*21)) -print(hamlib_version.contents.value) - - -''' -retcode = hamlib.rig_has_get_parm(rig, 7) -print(retcode) -''' - -''' -retcode = hamlib.rig_open(rig) -print(retcode) -hamlib.rig_close(rig) -''' - -# riginfo = create_string_buffer(1024) -# retcode = hamlib.rig_get_rig_info(rig, riginfo, 1024); - -''' -char riginfo[1024]; -retcode = rig_get_rig_info(rig, riginfo, sizeof(riginfo)); -''' diff --git a/modem/data cemetery/test/ping.py b/modem/data cemetery/test/ping.py deleted file mode 100644 index 01bf3e62..00000000 --- a/modem/data cemetery/test/ping.py +++ /dev/null @@ -1,214 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - - -import argparse -import ctypes -import pathlib -import threading -import time - -import pyaudio - -# --------------------------------------------GET PARAMETER INPUTS -parser = argparse.ArgumentParser(description="Simons TEST TNC") -parser.add_argument("--bursts", dest="N_BURSTS", default=0, type=int) -parser.add_argument("--frames", dest="N_FRAMES_PER_BURST", default=0, type=int) -parser.add_argument("--delay", dest="DELAY_BETWEEN_BURSTS", default=0, type=int) -parser.add_argument("--txmode", dest="FREEDV_TX_MODE", default=0, type=int) -parser.add_argument("--rxmode", dest="FREEDV_RX_MODE", default=0, type=int) -parser.add_argument("--audiooutput", dest="AUDIO_OUTPUT", default=0, type=int) -parser.add_argument("--audioinput", dest="AUDIO_INPUT", default=0, type=int) -parser.add_argument("--debug", dest="DEBUGGING_MODE", action="store_true") - -args, _ = parser.parse_known_args() - - -N_BURSTS = args.N_BURSTS -N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST -DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS / 1000 - -AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT -AUDIO_INPUT_DEVICE = args.AUDIO_INPUT - -# 1024 good for mode 6 -AUDIO_FRAMES_PER_BUFFER = 2048 -MODEM_SAMPLE_RATE = 8000 - -FREEDV_TX_MODE = args.FREEDV_TX_MODE -FREEDV_RX_MODE = args.FREEDV_RX_MODE - -DEBUGGING_MODE = args.DEBUGGING_MODE -# -------------------------------------------- LOAD FREEDV -libname = pathlib.Path().absolute() / "codec2/build_linux/src/libcodec2.so" -c_lib = ctypes.CDLL(str(libname)) - -# --------------------------------------------CREATE PYAUDIO INSTANCE -p = pyaudio.PyAudio() -# --------------------------------------------GET SUPPORTED SAMPLE RATES FROM SOUND DEVICE -# AUDIO_SAMPLE_RATE_TX = int(p.get_device_info_by_index(AUDIO_OUTPUT_DEVICE)['defaultSampleRate']) -# AUDIO_SAMPLE_RATE_RX = int(p.get_device_info_by_index(AUDIO_INPUT_DEVICE)['defaultSampleRate']) -AUDIO_SAMPLE_RATE_TX = 8000 -AUDIO_SAMPLE_RATE_RX = 8000 -# --------------------------------------------OPEN AUDIO CHANNEL TX - -stream_tx = p.open( - format=pyaudio.paInt16, - channels=1, - rate=AUDIO_SAMPLE_RATE_TX, - frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, # n_nom_modem_samples - output=True, - output_device_index=AUDIO_OUTPUT_DEVICE, -) - -stream_rx = p.open( - format=pyaudio.paInt16, - channels=1, - rate=AUDIO_SAMPLE_RATE_RX, - frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, - input=True, - input_device_index=AUDIO_INPUT_DEVICE, -) - - -def receive(): - - c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) - freedv = c_lib.freedv_open(FREEDV_RX_MODE) - bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv) / 8) - payload_per_frame = bytes_per_frame - 2 - n_nom_modem_samples = c_lib.freedv_get_n_nom_modem_samples(freedv) - n_tx_modem_samples = c_lib.freedv_get_n_tx_modem_samples( - freedv - ) # get n_tx_modem_samples which defines the size of the modulation object # --> *2 - - bytes_out = ctypes.c_ubyte * bytes_per_frame # bytes_per_frame - bytes_out = bytes_out() # get pointer from bytes_out - - rx_total_frames = 0 - rx_frames = 0 - rx_bursts = 0 - receive = True - total_n_bytes = 0 - while receive: - time.sleep(0.01) - - nin = c_lib.freedv_nin(freedv) - nin_converted = int(nin * (AUDIO_SAMPLE_RATE_RX / MODEM_SAMPLE_RATE)) - if DEBUGGING_MODE: - print("-----------------------------") - print(f"NIN: {str(nin)} [ {nin_converted} ]") - - data_in = stream_rx.read(nin_converted, exception_on_overflow=False) - data_in = data_in.rstrip(b"\x00") - - c_lib.freedv_rawdatarx.argtype = [ - ctypes.POINTER(ctypes.c_ubyte), - bytes_out, - data_in, - ] # check if really neccessary - nbytes = c_lib.freedv_rawdatarx(freedv, bytes_out, data_in) # demodulate audio - total_n_bytes = total_n_bytes + nbytes - if DEBUGGING_MODE: - print(f"SYNC: {str(c_lib.freedv_get_rx_status(freedv))}") - - if nbytes == bytes_per_frame: - rx_total_frames = rx_total_frames + 1 - rx_frames = rx_frames + 1 - - if rx_frames == N_FRAMES_PER_BURST: - rx_frames = 0 - rx_bursts = rx_bursts + 1 - c_lib.freedv_set_sync(freedv, 0) - - burst = bytes_out[0] - n_total_burst = bytes_out[1] - frame = bytes_out[2] - n_total_frame = bytes_out[3] - - print( - f"RX | PONG | BURST [{str(burst)}/{str(n_total_burst)}] FRAME [{str(frame)}/{str(n_total_frame)}]" - ) - print("-----------------------------------------------------------------") - c_lib.freedv_set_sync(freedv, 0) - - if rx_bursts == N_BURSTS: - receive = False - - -RECEIVE = threading.Thread(target=receive, name="RECEIVE THREAD") -RECEIVE.start() - - -c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) -freedv = c_lib.freedv_open(FREEDV_TX_MODE) -bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv) / 8) -n_nom_modem_samples = c_lib.freedv_get_n_nom_modem_samples(freedv) -n_tx_modem_samples = c_lib.freedv_get_n_tx_modem_samples( - freedv -) # get n_tx_modem_samples which defines the size of the modulation object # --> *2 - -mod_out = ctypes.c_short * n_tx_modem_samples -mod_out = mod_out() -mod_out_preamble = ctypes.c_short * ( - 1760 * 2 -) # 1760 for mode 10,11,12 #4000 for mode 9 -mod_out_preamble = mod_out_preamble() - - -print(f"BURSTS: {str(N_BURSTS)} FRAMES: {str(N_FRAMES_PER_BURST)}") -print("-----------------------------------------------------------------") - -payload_per_frame = bytes_per_frame - 2 -for i in range(N_BURSTS): - - c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble) - - txbuffer = bytearray() - txbuffer += bytes(mod_out_preamble) - - for n in range(N_FRAMES_PER_BURST): - - data_out = bytearray() - data_out += bytes([i + 1]) - data_out += bytes([N_BURSTS]) - data_out += bytes([n + 1]) - data_out += bytes([N_FRAMES_PER_BURST]) - - buffer = bytearray( - payload_per_frame - ) # use this if CRC16 checksum is required ( DATA1-3) - buffer[ - : len(data_out) - ] = data_out # set buffer size to length of data which will be sent - - crc = ctypes.c_ushort( - c_lib.freedv_gen_crc16(bytes(buffer), payload_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) - c_lib.freedv_rawdatatx( - freedv, mod_out, data - ) # modulate DATA and safe it into mod_out pointer - - txbuffer += bytes(mod_out) - - print( - f"TX | PING | BURST [{str(i + 1)}/{str(N_BURSTS)}] FRAME [{str(n + 1)}/{str(N_FRAMES_PER_BURST)}]" - ) - stream_tx.write(bytes(txbuffer)) - ACK_TIMEOUT = time.time() + 3 - txbuffer = bytearray() - - # time.sleep(DELAY_BETWEEN_BURSTS) - - # WAIT UNTIL WE RECEIVD AN ACK/datac13 FRAME - while ACK_TIMEOUT >= time.time(): - time.sleep(0.01) - - -time.sleep(1) -stream_tx.close() -p.terminate() diff --git a/modem/data cemetery/test/pong.py b/modem/data cemetery/test/pong.py deleted file mode 100644 index 7229d3ae..00000000 --- a/modem/data cemetery/test/pong.py +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Wed Dec 23 07:04:24 2020 - -@author: DJ2LS -""" - -import ctypes -from ctypes import * -import pathlib -import pyaudio -import sys -import logging -import time -import threading -import sys -import argparse - -# --------------------------------------------GET PARAMETER INPUTS -parser = argparse.ArgumentParser(description='Simons TEST TNC') -parser.add_argument('--bursts', dest="N_BURSTS", default=0, type=int) -parser.add_argument('--frames', dest="N_FRAMES_PER_BURST", default=0, type=int) -parser.add_argument('--txmode', dest="FREEDV_TX_MODE", default=0, type=int) -parser.add_argument('--rxmode', dest="FREEDV_RX_MODE", default=0, type=int) -parser.add_argument('--audioinput', dest="AUDIO_INPUT", default=0, type=int) -parser.add_argument('--audiooutput', dest="AUDIO_OUTPUT", default=0, type=int) -parser.add_argument('--debug', dest="DEBUGGING_MODE", action="store_true") - -args, _ = parser.parse_known_args() - -N_BURSTS = args.N_BURSTS -N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST - -AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT -AUDIO_INPUT_DEVICE = args.AUDIO_INPUT - -FREEDV_TX_MODE = args.FREEDV_TX_MODE -FREEDV_RX_MODE = args.FREEDV_RX_MODE - -DEBUGGING_MODE = args.DEBUGGING_MODE - -# 1024 good for mode 6 -AUDIO_FRAMES_PER_BUFFER = 2048 -MODEM_SAMPLE_RATE = 8000 - -# -------------------------------------------- LOAD FREEDV -libname = pathlib.Path().absolute() / "codec2/build_linux/src/libcodec2.so" -c_lib = ctypes.CDLL(libname) -# --------------------------------------------CREATE PYAUDIO INSTANCE -p = pyaudio.PyAudio() -# --------------------------------------------GET SUPPORTED SAMPLE RATES FROM SOUND DEVICE - -# AUDIO_SAMPLE_RATE_TX = int(p.get_device_info_by_index(AUDIO_OUTPUT_DEVICE)['defaultSampleRate']) -# AUDIO_SAMPLE_RATE_RX = int(p.get_device_info_by_index(AUDIO_INPUT_DEVICE)['defaultSampleRate']) -AUDIO_SAMPLE_RATE_TX = 8000 -AUDIO_SAMPLE_RATE_RX = 8000 -# --------------------------------------------OPEN AUDIO CHANNEL RX - -stream_tx = p.open(format=pyaudio.paInt16, - channels=1, - rate=AUDIO_SAMPLE_RATE_TX, - frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, # n_nom_modem_samples - output=True, - output_device_index=AUDIO_OUTPUT_DEVICE, - ) - -stream_rx = p.open(format=pyaudio.paInt16, - channels=1, - rate=AUDIO_SAMPLE_RATE_RX, - frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, - input=True, - input_device_index=AUDIO_INPUT_DEVICE, - ) - - -# GENERAL PARAMETERS -c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) - - -def send_pong(burst,n_total_burst,frame,n_total_frame): - - data_out = bytearray() - data_out[:1] = bytes([burst]) - data_out[1:2] = bytes([n_total_burst]) - data_out[2:3] = bytes([frame]) - data_out[4:5] = bytes([n_total_frame]) - - - - c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte) - freedv = c_lib.freedv_open(FREEDV_TX_MODE) - bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8) - payload_per_frame = bytes_per_frame -2 - n_nom_modem_samples = c_lib.freedv_get_n_nom_modem_samples(freedv) - n_tx_modem_samples = c_lib.freedv_get_n_tx_modem_samples(freedv) # get n_tx_modem_samples which defines the size of the modulation object # --> *2 - - mod_out = ctypes.c_short * n_tx_modem_samples - mod_out = mod_out() - mod_out_preamble = ctypes.c_short * (1760*2) # 1760 for mode 10,11,12 #4000 for mode 9 - mod_out_preamble = mod_out_preamble() - - buffer = bytearray(payload_per_frame) # use this if CRC16 checksum is required ( DATA1-3) - buffer[:len(data_out)] = data_out # set buffer size to length of data which will be sent - - crc = ctypes.c_ushort(c_lib.freedv_gen_crc16(bytes(buffer), payload_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 - - c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble); - txbuffer = bytearray() - txbuffer += bytes(mod_out_preamble) - - data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) - c_lib.freedv_rawdatatx(freedv,mod_out,data) # modulate DATA and safe it into mod_out pointer - - txbuffer += bytes(mod_out) - stream_tx.write(bytes(txbuffer)) - - txbuffer = bytearray() - - - - # DATA CHANNEL INITIALISATION - -freedv = c_lib.freedv_open(FREEDV_RX_MODE) -bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv)/8) -n_max_modem_samples = c_lib.freedv_get_n_max_modem_samples(freedv) -bytes_out = (ctypes.c_ubyte * bytes_per_frame) # bytes_per_frame -bytes_out = bytes_out() # get pointer from bytes_out - -receive = True -while receive: - time.sleep(0.01) - - data_in = b'' - - nin = c_lib.freedv_nin(freedv) - nin_converted = int(nin*(AUDIO_SAMPLE_RATE_RX/MODEM_SAMPLE_RATE)) - if DEBUGGING_MODE: - print("-----------------------------") - print(f"NIN: {str(nin)} [ {nin_converted} ]") - - data_in = stream_rx.read(nin_converted, exception_on_overflow = False) - data_in = data_in.rstrip(b'\x00') - - c_lib.freedv_rawdatarx.argtype = [ctypes.POINTER(ctypes.c_ubyte), bytes_out, data_in] # check if really neccessary - nbytes = c_lib.freedv_rawdatarx(freedv, bytes_out, data_in) # demodulate audio - - if DEBUGGING_MODE: - print(f"SYNC: {str(c_lib.freedv_get_rx_status(freedv))}") - - if nbytes == bytes_per_frame: - - burst = bytes_out[0] - n_total_burst = bytes_out[1] - frame = bytes_out[2] - n_total_frame = bytes_out[3] - print( - f"RX | BURST [{str(burst)}/{str(n_total_burst)}] FRAME [{str(frame)}/{str(n_total_frame)}] >>> SENDING PONG" - ) - - TRANSMIT_PONG = threading.Thread(target=send_pong, args=[burst,n_total_burst,frame,n_total_frame], name="SEND PONG") - TRANSMIT_PONG.start() - - c_lib.freedv_set_sync(freedv,0) diff --git a/modem/data cemetery/test/test_audiobuffer.py b/modem/data cemetery/test/test_audiobuffer.py deleted file mode 100644 index fd005458..00000000 --- a/modem/data cemetery/test/test_audiobuffer.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# tests audio buffer thread safety - -# pylint: disable=global-statement, invalid-name - -import sys -import threading -from time import sleep - -import codec2 -import numpy as np -import pytest - -BUFFER_SZ = 1024 -N_MAX = 100 # write a repeating sequence of 0....N_MAX-1 -WRITE_SZ = 10 # different read and write sized buffers -READ_SZ = 8 -NTESTS = 10000 - -running = True -audio_buffer = codec2.audio_buffer(BUFFER_SZ) - -n_write = 0 - - -def t_writer(): - """ - Subprocess to handle writes to the NumPY audio "device." - """ - global n_write - print("writer starting") - n = 0 - buf = np.zeros(WRITE_SZ, dtype=np.int16) - while running: - nfree = audio_buffer.size - audio_buffer.nbuffer - if nfree >= WRITE_SZ: - for index in range(WRITE_SZ): - buf[index] = n - n += 1 - if n == N_MAX: - n = 0 - n_write += 1 - audio_buffer.push(buf) - - -def test_audiobuffer(): - """ - Test for the audiobuffer - """ - global running - - # Start the writer in a new thread. - writer_thread = threading.Thread(target=t_writer) - writer_thread.start() - - n_out = n_read = errors = 0 - for _ in range(NTESTS): - while audio_buffer.nbuffer < READ_SZ: - sleep(0.001) - for i in range(READ_SZ): - if audio_buffer.buffer[i] != n_out: - errors += 1 - n_out += 1 - if n_out == N_MAX: - n_out = 0 - n_read += 1 - audio_buffer.pop(READ_SZ) - - print(f"n_write: {n_write} n_read: {n_read} errors: {errors} ") - - # Indirectly stop the thread - running = False - sleep(0.1) - - assert not writer_thread.is_alive() - assert n_write - n_read < BUFFER_SZ - assert errors == 0 - - -if __name__ == "__main__": - # Run pytest with the current script as the filename. - ecode = pytest.main(["-v", sys.argv[0]]) - if ecode == 0: - print("errors: 0") - else: - print(ecode) diff --git a/modem/data cemetery/test/test_chat_text.py b/modem/data cemetery/test/test_chat_text.py deleted file mode 100644 index ddc1f010..00000000 --- a/modem/data cemetery/test/test_chat_text.py +++ /dev/null @@ -1,237 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Test small (single-frame) and large (multi-frame) messages over a high quality -simulated audio channel. - -Near end-to-end test for sending / receiving data through the TNC and modem -and back through on the other station. Data injection initiates from the -queue used by the daemon process into and out of the TNC. Tests both low- and -high-bandwidth data frames (datac3 and datac1 respectively) from Codec2. - -Can be invoked from CMake, pytest, coverage or directly. - -Uses util_chat_test_[12].py in separate processes to perform the data transfer. - -@author: N2KIQ -""" - -import contextlib -import multiprocessing -import os -import sys -import threading -import time -import zlib - -import helpers -import log_handler -import pytest -import structlog - -try: - import test.util_chat_text_1 as util1 - import test.util_chat_text_2 as util2 -except ImportError: - import util_chat_text_1 as util1 - import util_chat_text_2 as util2 - - -STATIONS = ["AA2BB", "ZZ9YY"] - -bytes_out = b'{"dt":"f","fn":"zeit.txt","ft":"text\\/plain","d":"data:text\\/plain;base64,MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=","crc":"123123123"}' - -messages = [ - "This is a test chat...", - "This is a much longer message, hopefully longer than each of the datac1 and " - "datac3 frames available to use in this modem. This should be long enough, " - "but to err on the side of completeness this will string on for many more " - "words before coming to the long awaited conclusion. We are not at the " - "concluding point just yet because there is still more space to be taken up " - "in the datac3 frame. Perhaps now would be a good place to terminate this test " - "message, but perhaps not because we need a few more bytes. Here then we stop. " - "This compresses so well that I need more data, even more stuff than is already " - "here and included in the unreadable diatribe below, or is it a soliloquy? " - "MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obm" - "UgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5" - "NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0" - "MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG" - "9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=MyBtb2Rlcywgb2huZSBjbGFzcwowL" - "jAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODk" - "xMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2R" - "lcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc" - "3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY" - "1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjI" - "gbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY" - "2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=", -] -PIPE_THREAD_RUNNING = True - - -def locate_data_with_crc(source_list: list, text: str, data: bytes, frametype: str): - """Try to locate data in source_list.""" - log = structlog.get_logger("locate_data_with_crc") - - if data in source_list: - with contextlib.suppress(): - data = zlib.decompress(data[2:]) - log.info(f"analyze_results: {text} no CRC", _frametype=frametype, data=data) - elif data + helpers.get_crc_8(data) in source_list: - with contextlib.suppress(zlib.error): - data = zlib.decompress(data[2:-1]) - log.info(f"analyze_results: {text} CRC 8", _frametype=frametype, data=data) - elif data + helpers.get_crc_16(data) in source_list: - with contextlib.suppress(zlib.error): - data = zlib.decompress(data[2:-2]) - log.info(f"analyze_results: {text} CRC16", _frametype=frametype, data=data) - elif data + helpers.get_crc_24(data) in source_list: - with contextlib.suppress(zlib.error): - data = zlib.decompress(data[2:-3]) - log.info(f"analyze_results: {text} CRC24", _frametype=frametype, data=data) - elif data + helpers.get_crc_32(data) in source_list: - with contextlib.suppress(zlib.error): - data = zlib.decompress(data[2:-4]) - log.info(f"analyze_results: {text} CRC32", _frametype=frametype, data=data) - else: - log.info( - f"analyze_results: {text} not received:", - _frame=frametype, - data=data, - ) - - -def analyze_results(station1: list, station2: list, call_list: list): - """Examine the information retrieved from the sub-processes.""" - # Data in these lists is either a series of bytes of received data, - # or a bytearray of transmitted data from the station. - log = structlog.get_logger("analyze_results") - - # Check that each station's transmitted data was received by the other. - for s1, s2, text in [ - (station1, station2, call_list[0]), - (station2, station1, call_list[1]), - ]: - for s1_item in s1: - if not isinstance(s1_item, list): - continue - data = bytes(s1_item[0]) - frametypeno = int.from_bytes(data[:1], "big") - # frametype = static.FRAME_TYPE(frametypeno).name - frametype = str(frametypeno) - s1_crc = helpers.decode_call(helpers.bytes_to_callsign(data[1:4])) - s2_crc = helpers.decode_call(helpers.bytes_to_callsign(data[2:5])) - log.info( - "analyze_results: callsign CRCs:", - tx_station=text, - s1_crc=s1_crc, - s2_crc=s2_crc, - ) - - locate_data_with_crc(s2, text, data, frametype) - - -@pytest.mark.parametrize("freedv_mode", ["datac1", "datac3"]) -@pytest.mark.parametrize("n_frames_per_burst", [1]) # Higher fpb is broken. -@pytest.mark.parametrize("message_no", range(len(messages))) -@pytest.mark.flaky(reruns=3) -def test_chat_text( - freedv_mode: str, n_frames_per_burst: int, message_no: int, tmp_path -): - log_handler.setup_logging(filename=tmp_path / "test_chat_text", level="INFO") - log = structlog.get_logger("test_chat_text") - - s1_data = [] - s2_data = [] - - def recv_data(buffer: list, pipe): - while PIPE_THREAD_RUNNING: - if pipe.poll(0.1): - buffer.append(pipe.recv()) - else: - time.sleep(0.1) - - def recv_from_pipes(s1_rx, s1_pipe, s2_rx, s2_pipe) -> list: - processes = [ - threading.Thread(target=recv_data, args=(s1_rx, s1_pipe)), - threading.Thread(target=recv_data, args=(s2_rx, s2_pipe)), - ] - for item in processes: - item.start() - - return processes - - # This sufficiently separates the two halves of the test. This is needed - # because both scripts change global state. They would conflict if running in - # the same process. - from_s1, s1_send = multiprocessing.Pipe() - from_s2, s2_send = multiprocessing.Pipe() - proc = [ - multiprocessing.Process( - target=util1.t_highsnr_arq_short_station1, - args=( - s1_send, - freedv_mode, - n_frames_per_burst, - STATIONS[0], - STATIONS[1], - messages[message_no], - freedv_mode == "datac3", # == low bandwidth mode - tmp_path, - ), - daemon=True, - ), - multiprocessing.Process( - target=util2.t_highsnr_arq_short_station2, - args=( - s2_send, - freedv_mode, - n_frames_per_burst, - STATIONS[1], - STATIONS[0], - messages[message_no], - freedv_mode == "datac3", # == low bandwidth mode - tmp_path, - ), - daemon=True, - ), - ] - - pipe_receivers = recv_from_pipes(s1_data, from_s1, s2_data, from_s2) - # log.debug("Creating ") - # print("Starting threads.") - for p_item in proc: - p_item.start() - - # This relies on each process exiting when its job is complete! - - # print("Waiting for threads to exit.") - for p_item in proc: - p_item.join() - - global PIPE_THREAD_RUNNING # pylint: disable=global-statement - PIPE_THREAD_RUNNING = False - for pipe_recv in pipe_receivers: - pipe_recv.join() - - for idx in range(2): - try: - os.unlink(tmp_path / f"hfchannel{idx+1}") - except FileNotFoundError as fnfe: - log.debug(f"Unlinking pipe: {fnfe}") - - for p_item in proc: - assert p_item.exitcode == 0 - # p_item.close() # Python 3.7+ only - p_item.terminate() - p_item.join() - - analyze_results(s1_data, s2_data, STATIONS) - - -if __name__ == "__main__": - # Run pytest with the current script as the filename. - ecode = pytest.main(["-s", "-v", sys.argv[0]]) - if ecode == 0: - print("errors: 0") - else: - print(ecode) diff --git a/modem/data cemetery/test/test_datac13.py b/modem/data cemetery/test/test_datac13.py deleted file mode 100644 index 55f1cc73..00000000 --- a/modem/data cemetery/test/test_datac13.py +++ /dev/null @@ -1,291 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Test control frame commands over a high quality simulated audio channel. - -Near end-to-end test for sending / receiving select control frames through the -TNC and modem and back through on the other station. Data injection initiates from the -queue used by the daemon process into and out of the TNC. - -Can be invoked from CMake, pytest, coverage or directly. - -Uses util_datac13.py in separate process to perform the data transfer. - -@author: N2KIQ -""" - -import contextlib -import multiprocessing -import os -import sys -import threading -import time -import zlib - -import helpers -import log_handler -import pytest -import structlog - -try: - import test.util_datac13 as util -except ImportError: - import util_datac13 as util - - -STATIONS = ["AA2BB", "ZZ9YY"] - -PIPE_THREAD_RUNNING = True - - -def parameters() -> dict: - # Construct message to start beacon. - beacon_data = {"type": "command", "command": "start_beacon", "parameter": "5"} - # Construct message to start cq. - cq_data = {"type": "command", "command": "cqcqcq"} - # Construct message to start ping. - ping_data = {"type": "ping", "command": "ping", "dxcallsign": "ZZ9YY-0"} - connect_data = {"type": "arq", "command": "connect", "dxcallsign": "ZZ9YY-0"} - stop_data = {"type": "arq", "command": "stop_transmission", "dxcallsign": "ZZ9YY-0"} - - beacon_timeout = 1 - ping_timeout = 1 - cq_timeout = 1 - connect_timeout = 1 - stop_timeout = 1 - - beacon_tx_check = '"beacon":"transmitting"' - cq_tx_check = '"qrv":"received"' - ping_tx_check = '"ping":"transmitting"' - connect_tx_check = '"session":"connecting"' - stop_tx_check = '"status":"stopped"' - - beacon_rx_check = '"beacon":"received"' - cq_rx_check = '"cq":"received"' - ping_rx_check = '"ping":"received"' - connect_rx_check = '"connect":"received"' - stop_rx_check = '"status":"stopped"' - - beacon_final_tx_check = [beacon_tx_check] - cq_final_tx_check = ['"cq":"transmitting"', cq_tx_check] - ping_final_tx_check = [ping_tx_check, '"ping":"acknowledge"'] - connect_final_tx_check = ['"status":"connected"', '"connect":"acknowledge"'] - stop_final_tx_check = [stop_tx_check] - - beacon_final_rx_check = [beacon_rx_check] - cq_final_rx_check = [cq_rx_check, '"qrv":"transmitting"'] - ping_final_rx_check = [ping_rx_check] - connect_final_rx_check = [connect_rx_check] - stop_final_rx_check = [stop_rx_check] - - return { - "beacon": ( - beacon_data, - beacon_timeout, - beacon_tx_check, - beacon_rx_check, - beacon_final_tx_check, - beacon_final_rx_check, - ), - "connect": ( - connect_data, - connect_timeout, - connect_tx_check, - connect_rx_check, - connect_final_tx_check, - connect_final_rx_check, - ), - "cq": ( - cq_data, - cq_timeout, - cq_tx_check, - cq_rx_check, - cq_final_tx_check, - cq_final_rx_check, - ), - "ping": ( - ping_data, - ping_timeout, - ping_tx_check, - ping_rx_check, - ping_final_tx_check, - ping_final_rx_check, - ), - "stop": ( - stop_data, - stop_timeout, - stop_tx_check, - stop_rx_check, - stop_final_tx_check, - stop_final_rx_check, - ), - } - - -def locate_data_with_crc(source_list: list, text: str, data: bytes, frametype: str): - """Try to locate data in source_list.""" - log = structlog.get_logger("locate_data_with_crc") - - if data in source_list: - with contextlib.suppress(): - data = zlib.decompress(data[2:]) - log.info(f"analyze_results: {text} no CRC", _frametype=frametype, data=data) - elif data + helpers.get_crc_8(data) in source_list: - with contextlib.suppress(zlib.error): - data = zlib.decompress(data[2:-1]) - log.info(f"analyze_results: {text} CRC 8", _frametype=frametype, data=data) - elif data + helpers.get_crc_16(data) in source_list: - with contextlib.suppress(zlib.error): - data = zlib.decompress(data[2:-2]) - log.info(f"analyze_results: {text} CRC16", _frametype=frametype, data=data) - elif data + helpers.get_crc_24(data) in source_list: - with contextlib.suppress(zlib.error): - data = zlib.decompress(data[2:-3]) - log.info(f"analyze_results: {text} CRC24", _frametype=frametype, data=data) - elif data + helpers.get_crc_32(data) in source_list: - with contextlib.suppress(zlib.error): - data = zlib.decompress(data[2:-4]) - log.info(f"analyze_results: {text} CRC32", _frametype=frametype, data=data) - else: - log.info( - f"analyze_results: {text} not received:", - _frame=frametype, - data=data, - ) - - -def analyze_results(station1: list, station2: list, call_list: list): - """Examine the information retrieved from the sub-processes.""" - # Data in these lists is either a series of bytes of received data, - # or a bytearray of transmitted data from the station. - log = structlog.get_logger("analyze_results") - - # Check that each station's transmitted data was received by the other. - for s1, s2, text in [ - (station1, station2, call_list[0]), - (station2, station1, call_list[1]), - ]: - for s1_item in s1: - if not isinstance(s1_item, list): - continue - data = bytes(s1_item[0]) - frametypeno = int.from_bytes(data[:1], "big") - # frametype = static.FRAME_TYPE(frametypeno).name - frametype = str(frametypeno) - s1_crc = helpers.decode_call(helpers.bytes_to_callsign(data[1:4])) - s2_crc = helpers.decode_call(helpers.bytes_to_callsign(data[2:5])) - log.info( - "analyze_results: callsign CRCs:", - tx_station=text, - s1_crc=s1_crc, - s2_crc=s2_crc, - ) - - locate_data_with_crc(s2, text, data, frametype) - - -# frame_type "connect" doesn't work 2022-Jun-16. Missing / incomplete SOCKET_QUEUE data. -# frame_type "cq" is overly flaky. Can't get the timing right / FIFO not delivering data. -@pytest.mark.parametrize( - "frame_type", - [ - pytest.param("beacon", marks=pytest.mark.flaky(reruns=2)), - pytest.param("ping", marks=pytest.mark.flaky(reruns=2)), - # FIXME: pytest.param("cq", marks=pytest.mark.flaky(reruns=20)), - #pytest.param("cq", marks=pytest.mark.xfail(reason="Too unstable for CI")), - pytest.param("stop", marks=pytest.mark.flaky(reruns=2)), - ], -) -def test_datac13(frame_type: str, tmp_path): - log_handler.setup_logging(filename=tmp_path / "test_datac13", level="DEBUG") - log = structlog.get_logger("test_datac13") - - s1_data = [] - s2_data = [] - - def recv_data(buffer: list, pipe): - while PIPE_THREAD_RUNNING: - if pipe.poll(0.1): - buffer.append(pipe.recv()) - else: - time.sleep(0.1) - - def recv_from_pipes(s1_rx, s1_pipe, s2_rx, s2_pipe) -> list: - processes = [ - threading.Thread(target=recv_data, args=(s1_rx, s1_pipe)), - threading.Thread(target=recv_data, args=(s2_rx, s2_pipe)), - ] - for item in processes: - item.start() - - return processes - - # This sufficiently separates the two halves of the test. This is needed - # because both scripts change global state. They would conflict if running in - # the same process. - from_s1, s1_send = multiprocessing.Pipe() - from_s2, s2_send = multiprocessing.Pipe() - proc = [ - multiprocessing.Process( - target=util.t_datac13_1, - args=( - s1_send, - STATIONS[0], - STATIONS[1], - parameters()[frame_type], - tmp_path, - ), - daemon=True, - ), - multiprocessing.Process( - target=util.t_datac13_2, - args=( - s2_send, - STATIONS[1], - STATIONS[0], - parameters()[frame_type], - tmp_path, - ), - daemon=True, - ), - ] - - pipe_receivers = recv_from_pipes(s1_data, from_s1, s2_data, from_s2) - # log.debug("Creating ") - # print("Starting threads.") - for p_item in proc: - p_item.start() - - # This relies on each process exiting when its job is complete! - - # print("Waiting for threads to exit.") - for p_item in proc: - p_item.join() - - global PIPE_THREAD_RUNNING # pylint: disable=global-statement - PIPE_THREAD_RUNNING = False - for pipe_recv in pipe_receivers: - pipe_recv.join() - - for idx in range(2): - try: - os.unlink(tmp_path / f"hfchannel{idx+1}") - except FileNotFoundError as fnfe: - log.debug(f"Unlinking pipe: {fnfe}") - - for p_item in proc: - assert p_item.exitcode == 0 - # p_item.close() # Python 3.7+ only - p_item.terminate() - p_item.join() - - analyze_results(s1_data, s2_data, STATIONS) - - -if __name__ == "__main__": - # Run pytest with the current script as the filename. - ecode = pytest.main(["-s", "-v", sys.argv[0]]) - if ecode == 0: - print("errors: 0") - else: - print(ecode) diff --git a/modem/data cemetery/test/test_datac13_negative.py b/modem/data cemetery/test/test_datac13_negative.py deleted file mode 100644 index 699fd487..00000000 --- a/modem/data cemetery/test/test_datac13_negative.py +++ /dev/null @@ -1,261 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Negative tests for datac13 frames. - -@author: kronenpj -""" - -import contextlib -import multiprocessing -import os -import sys -import threading -import time -import zlib - -import helpers -import log_handler -import pytest -import structlog - -try: - import test.util_datac13_negative as util -except ImportError: - import util_datac13_negative as util - - -STATIONS = ["AA2BB", "ZZ9YY"] - -PIPE_THREAD_RUNNING = True - - -def parameters() -> dict: - # Construct message to start beacon. - beacon_data = {"type": "command", "command": "start_beacon", "parameter": "-5"} - # Construct message to start ping. - ping_data = {"type": "ping", "command": "ping", "dxcallsign": ""} - connect_data = {"type": "arq", "command": "connect", "dxcallsign": ""} - stop_data = {"type": "arq", "command": "stop_transmission", "dxcallsign": "DD5GG-3"} - - beacon_timeout = 1 - ping_timeout = 1 - connect_timeout = 1 - stop_timeout = 1 - - beacon_tx_check = '"status":"Failed"' - ping_tx_check = '"ping","status":"Failed"' - connect_tx_check = '"status":"Failed"' - stop_tx_check = '"status":"stopped"' - - beacon_rx_check = '"beacon":"received"' - ping_rx_check = '"ping":"received"' - connect_rx_check = '"connect":"received"' - stop_rx_check = '"status":"stopped"' - - beacon_final_tx_check = [beacon_tx_check] - ping_final_tx_check = [ping_tx_check] - connect_final_tx_check = [connect_tx_check] - stop_final_tx_check = [stop_tx_check] - - beacon_final_rx_check = [beacon_rx_check] - ping_final_rx_check = [ping_rx_check] - connect_final_rx_check = [connect_rx_check] - stop_final_rx_check = [stop_rx_check] - - return { - "beacon": ( - beacon_data, - beacon_timeout, - beacon_tx_check, - beacon_rx_check, - beacon_final_tx_check, - beacon_final_rx_check, - ), - "connect": ( - connect_data, - connect_timeout, - connect_tx_check, - connect_rx_check, - connect_final_tx_check, - connect_final_rx_check, - ), - "ping": ( - ping_data, - ping_timeout, - ping_tx_check, - ping_rx_check, - ping_final_tx_check, - ping_final_rx_check, - ), - "stop": ( - stop_data, - stop_timeout, - stop_tx_check, - stop_rx_check, - stop_final_tx_check, - stop_final_rx_check, - ), - } - - -def locate_data_with_crc(source_list: list, text: str, data: bytes, frametype: str): - """Try to locate data in source_list.""" - log = structlog.get_logger("locate_data_with_crc") - - if data in source_list: - with contextlib.suppress(): - data = zlib.decompress(data[2:]) - log.info(f"analyze_results: {text} no CRC", _frametype=frametype, data=data) - elif data + helpers.get_crc_8(data) in source_list: - with contextlib.suppress(zlib.error): - data = zlib.decompress(data[2:-1]) - log.info(f"analyze_results: {text} CRC 8", _frametype=frametype, data=data) - elif data + helpers.get_crc_16(data) in source_list: - with contextlib.suppress(zlib.error): - data = zlib.decompress(data[2:-2]) - log.info(f"analyze_results: {text} CRC16", _frametype=frametype, data=data) - elif data + helpers.get_crc_24(data) in source_list: - with contextlib.suppress(zlib.error): - data = zlib.decompress(data[2:-3]) - log.info(f"analyze_results: {text} CRC24", _frametype=frametype, data=data) - elif data + helpers.get_crc_32(data) in source_list: - with contextlib.suppress(zlib.error): - data = zlib.decompress(data[2:-4]) - log.info(f"analyze_results: {text} CRC32", _frametype=frametype, data=data) - else: - log.info( - f"analyze_results: {text} not received:", - _frame=frametype, - data=data, - ) - - -def analyze_results(station1: list, station2: list, call_list: list): - """Examine the information retrieved from the sub-processes.""" - # Data in these lists is either a series of bytes of received data, - # or a bytearray of transmitted data from the station. - log = structlog.get_logger("analyze_results") - - # Check that each station's transmitted data was received by the other. - for s1, s2, text in [ - (station1, station2, call_list[0]), - (station2, station1, call_list[1]), - ]: - for s1_item in s1: - if not isinstance(s1_item, list): - continue - data = bytes(s1_item[0]) - frametypeno = int.from_bytes(data[:1], "big") - # frametype = static.FRAME_TYPE(frametypeno).name - frametype = str(frametypeno) - s1_crc = helpers.decode_call(helpers.bytes_to_callsign(data[1:4])) - s2_crc = helpers.decode_call(helpers.bytes_to_callsign(data[2:5])) - log.info( - "analyze_results: callsign CRCs:", - tx_station=text, - s1_crc=s1_crc, - s2_crc=s2_crc, - ) - - locate_data_with_crc(s2, text, data, frametype) - - -# @pytest.mark.parametrize("frame_type", ["beacon", "connect", "ping"]) -@pytest.mark.parametrize("frame_type", [ - "ping", - pytest.param("stop", marks=pytest.mark.flaky(reruns=10)) -]) -def test_datac13_negative(frame_type: str, tmp_path): - log_handler.setup_logging(filename=tmp_path / "test_datac13", level="DEBUG") - log = structlog.get_logger("test_datac13") - - s1_data = [] - s2_data = [] - - def recv_data(buffer: list, pipe): - while PIPE_THREAD_RUNNING: - if pipe.poll(0.1): - buffer.append(pipe.recv()) - else: - time.sleep(0.1) - - def recv_from_pipes(s1_rx, s1_pipe, s2_rx, s2_pipe) -> list: - processes = [ - threading.Thread(target=recv_data, args=(s1_rx, s1_pipe)), - threading.Thread(target=recv_data, args=(s2_rx, s2_pipe)), - ] - for item in processes: - item.start() - - return processes - - # This sufficiently separates the two halves of the test. This is needed - # because both scripts change global state. They would conflict if running in - # the same process. - from_s1, s1_send = multiprocessing.Pipe() - from_s2, s2_send = multiprocessing.Pipe() - proc = [ - multiprocessing.Process( - target=util.t_datac13_1, - args=( - s1_send, - STATIONS[0], - STATIONS[1], - parameters()[frame_type], - tmp_path, - ), - daemon=True, - ), - multiprocessing.Process( - target=util.t_datac13_2, - args=( - s2_send, - STATIONS[1], - STATIONS[0], - parameters()[frame_type], - tmp_path, - ), - daemon=True, - ), - ] - - pipe_receivers = recv_from_pipes(s1_data, from_s1, s2_data, from_s2) - # log.debug("Creating ") - # print("Starting threads.") - for p_item in proc: - p_item.start() - - # This relies on each process exiting when its job is complete! - - # print("Waiting for threads to exit.") - for p_item in proc: - p_item.join() - - global PIPE_THREAD_RUNNING # pylint: disable=global-statement - PIPE_THREAD_RUNNING = False - for pipe_recv in pipe_receivers: - pipe_recv.join() - - for idx in range(2): - try: - os.unlink(tmp_path / f"hfchannel{idx+1}") - except FileNotFoundError as fnfe: - log.debug(f"Unlinking pipe: {fnfe}") - - for p_item in proc: - assert p_item.exitcode == 0 - # p_item.close() # Python 3.7+ only - p_item.terminate() - p_item.join() - - analyze_results(s1_data, s2_data, STATIONS) - - -if __name__ == "__main__": - # Run pytest with the current script as the filename. - ecode = pytest.main(["-s", "-v", sys.argv[0]]) - if ecode == 0: - print("errors: 0") - else: - print(ecode) diff --git a/modem/data cemetery/test/test_helpers.py b/modem/data cemetery/test/test_helpers.py deleted file mode 100644 index fb043c8b..00000000 --- a/modem/data cemetery/test/test_helpers.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Unit test common helper routines used throughout the Modem. - -Can be invoked from CMake, pytest, coverage or directly. - -Uses no other files. - -@author: N2KIQ -""" - -import sys - -import helpers -import pytest -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem - - -@pytest.mark.parametrize("callsign", ["AA1AA", "DE2DE", "E4AWQ-4"]) -def test_check_callsign(callsign: str): - """ - Execute test to demonstrate how to create and verify callsign checksums. - """ - Station.ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] - - t_callsign_bytes = helpers.callsign_to_bytes(callsign) - t_callsign = helpers.bytes_to_callsign(t_callsign_bytes) - t_callsign_crc = helpers.get_crc_24(t_callsign) - - dxcallsign_bytes = helpers.callsign_to_bytes("ZZ9ZZA-0") - dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes) - dxcallsign_crc = helpers.get_crc_24(dxcallsign) - - assert helpers.check_callsign(t_callsign, t_callsign_crc)[0] is True - assert helpers.check_callsign(t_callsign, dxcallsign_crc)[0] is False - - -@pytest.mark.parametrize("callsign", ["AA1AA-2", "DE2DE-0", "E4AWQ-4"]) -def test_callsign_to_bytes(callsign: str): - """ - Execute test to demonsrate symmetry when converting callsigns to and from byte arrays. - """ - Station.ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] - - t_callsign_crc = helpers.get_crc_24(bytes(callsign, "UTF-8")) - t_callsign_bytes = helpers.callsign_to_bytes(callsign) - t_callsign = helpers.bytes_to_callsign(t_callsign_bytes) - - assert helpers.check_callsign(t_callsign, t_callsign_crc)[0] is True - assert helpers.check_callsign(t_callsign, t_callsign_crc)[1] == bytes( - callsign, "UTF-8" - ) - - -@pytest.mark.parametrize("callsign", ["AA1AA-2", "DE2DE-0", "e4awq-4"]) -def test_encode_callsign(callsign: str): - """ - Execute test to demonsrate symmetry when encoding and decoding callsigns. - """ - callenc = helpers.encode_call(callsign) - calldec = helpers.decode_call(callenc) - - assert callsign.upper() != calldec - - -@pytest.mark.parametrize("gridsq", ["EM98dc", "DE01GG", "EF42sW"]) -def test_encode_grid(gridsq: str): - """ - Execute test to demonsrate symmetry when encoding and decoding grid squares. - """ - - gridenc = helpers.encode_grid(gridsq) - griddec = helpers.decode_grid(gridenc) - - assert gridsq.upper() == griddec - - -@pytest.mark.parametrize("gridsq", ["SM98dc", "DE01GZ", "EV42sY"]) -@pytest.mark.xfail(reason="Invalid gridsquare provided") -def test_invalid_grid(gridsq: str): - """ - Execute test to demonsrate symmetry when encoding and decoding grid squares. - """ - - gridenc = helpers.encode_grid(gridsq) - griddec = helpers.decode_grid(gridenc) - - assert gridsq.upper() != griddec - - -if __name__ == "__main__": - # Run pytest with the current script as the filename. - ecode = pytest.main(["-v", sys.argv[0]]) - if ecode == 0: - print("errors: 0") - else: - print(ecode) diff --git a/modem/data cemetery/test/test_highsnr_stdio_C_P_datacx.py b/modem/data cemetery/test/test_highsnr_stdio_C_P_datacx.py deleted file mode 100644 index 231ff380..00000000 --- a/modem/data cemetery/test/test_highsnr_stdio_C_P_datacx.py +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Test small multiple-burst messages over a high quality simulated audio channel. - -Legacy test for sending / receiving frames through the codec2 modem -and back through on the other station. Data injection initiates directly into -codec2 API. Tests all three codec2 data frames. - -Can be invoked from CMake, pytest, coverage or directly. - -Uses util_rx.py, sox and freedv_data_raw_tx in separate processeses to perform -the audio tests. - -@author: DJ2LS, N2KIQ -""" - -# pylint: disable=global-statement, invalid-name, unused-import - -import contextlib -import glob -import multiprocessing -import os -import subprocess -import sys -import time - -import pytest - -BURSTS = 1 -FRAMESPERBURST = 1 -TESTFRAMES = 3 - -with contextlib.suppress(KeyError): - BURSTS = int(os.environ["BURSTS"]) -with contextlib.suppress(KeyError): - FRAMESPERBURST = int(os.environ["FRAMESPERBURST"]) -with contextlib.suppress(KeyError): - TESTFRAMES = int(os.environ["TESTFRAMES"]) - -# For some reason, sometimes, this test requires the current directory to be `test`. -# Try to adapt dynamically. I still want to figure out why but as a workaround, -# I'm not completely dissatisfied. -if os.path.exists("test"): - os.chdir("test") - - -def t_HighSNR_C_P_DATACx( - bursts: int, frames_per_burst: int, testframes: int, mode: str -): - """ - Test a high signal-to-noise ratio path with datac13. - - :param bursts: Number of bursts - :type bursts: str - :param frames_per_burst: Number of frames transmitted per burst - :type frames_per_burst: str - :param testframes: Number of test frames to transmit - :type testframes: str - """ - # Facilitate running from main directory as well as inside test/ - rx_side = "util_rx.py" - if os.path.exists("test") and os.path.exists(os.path.join("test", rx_side)): - rx_side = os.path.join("test", rx_side) - os.environ["PYTHONPATH"] += ":." - - tx_side = "freedv_data_raw_tx" - _txpaths = ( - os.path.join("..", "modem") - if os.path.exists(os.path.join("..", "modem")) - else "modem" - ) - _txpaths = glob.glob(rf"{_txpaths}/**/{tx_side}", recursive=True) - for path in _txpaths: - tx_side = path - break - - print(f"tx_side={tx_side} / rx_side={rx_side}") - - with subprocess.Popen( - args=[ - tx_side, - mode, - "--testframes", - f"{testframes}", - "--bursts", - f"{bursts}", - "--framesperburst", - f"{frames_per_burst}", - "/dev/zero", - "-", - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) as transmit: - - with subprocess.Popen( - args=[ - "sox", - "-t", - ".s16", - "-r", - "8000", - "-c", - "1", - "-", - "-t", - ".s16", - "-r", - "48000", - "-c", - "1", - "-", - ], - stdin=transmit.stdout, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) as sox_filter: - - with subprocess.Popen( - args=[ - "python3", - rx_side, - "--mode", - mode, - "--framesperburst", - str(frames_per_burst), - "--bursts", - str(bursts), - ], - stdin=sox_filter.stdout, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) as receive: - assert receive.stdout - lastline = "".join( - [ - str(line, "UTF-8") - for line in receive.stdout.readlines() - if "RECEIVED " in str(line, "UTF-8") - ] - ) - assert f"RECEIVED BURSTS: {bursts}" in lastline - assert f"RECEIVED FRAMES: {frames_per_burst * bursts}" in lastline - assert "RX_ERRORS: 0" in lastline - print(lastline) - - -# @pytest.mark.parametrize("bursts", [BURSTS, 2, 3]) -# @pytest.mark.parametrize("frames_per_burst", [FRAMESPERBURST, 2, 3]) -# @pytest.mark.parametrize("testframes", [TESTFRAMES, 2, 1]) -@pytest.mark.parametrize("bursts", [BURSTS]) -@pytest.mark.parametrize("frames_per_burst", [FRAMESPERBURST]) -@pytest.mark.parametrize("testframes", [TESTFRAMES]) -@pytest.mark.parametrize("mode", ["datac13", "datac1", "datac3"]) -def test_HighSNR_C_P_DATACx( - bursts: int, frames_per_burst: int, testframes: int, mode: str -): - proc = multiprocessing.Process( - target=t_HighSNR_C_P_DATACx, - args=[bursts, frames_per_burst, testframes, mode], - daemon=True, - ) - - proc.start() - - # Set timeout - timeout = time.time() + 5 - - while time.time() < timeout: - time.sleep(0.1) - - if proc.is_alive(): - proc.terminate() - assert 0, "Timeout waiting for test to complete." - - proc.join() - proc.terminate() - - assert proc.exitcode == 0 - # proc.close() # Python 3.7+ only - proc.terminate() - proc.join() - - -if __name__ == "__main__": - # Run pytest with the current script as the filename. - ecode = pytest.main(["-v", "-s", sys.argv[0]]) - if ecode == 0: - print("errors: 0") - else: - print(ecode) diff --git a/modem/data cemetery/test/test_highsnr_stdio_P_C_datacx.py b/modem/data cemetery/test/test_highsnr_stdio_P_C_datacx.py deleted file mode 100644 index 81cde7a5..00000000 --- a/modem/data cemetery/test/test_highsnr_stdio_P_C_datacx.py +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Test small multiple-burst messages over a high quality simulated audio channel. - -Legacy test for sending / receiving frames through the codec2 modem -and back through on the other station. Data injection initiates directly into -codec2 API. Tests all three codec2 data frames. - -Can be invoked from CMake, pytest, coverage or directly. - -Uses util_tx.py, sox, freedv_data_raw_rx and hexdump in separate processeses to -perform the audio tests. - -@author: N2KIQ -""" - -# pylint: disable=global-statement, invalid-name, unused-import - -import contextlib -import glob -import multiprocessing -import os -import subprocess -import sys -import time - -import pytest - -BURSTS = 1 -FRAMESPERBURST = 1 -TESTFRAMES = 3 - -with contextlib.suppress(KeyError): - BURSTS = int(os.environ["BURSTS"]) -with contextlib.suppress(KeyError): - FRAMESPERBURST = int(os.environ["FRAMESPERBURST"]) -with contextlib.suppress(KeyError): - TESTFRAMES = int(os.environ["TESTFRAMES"]) - -# For some reason, sometimes, this test requires the current directory to be `test`. -# Try to adapt dynamically. I still want to figure out why but as a workaround, -# I'm not completely dissatisfied. -if os.path.exists("test"): - os.chdir("test") - - -def t_HighSNR_P_C_DATACx(bursts: int, frames_per_burst: int, mode: str): - """ - Test a high signal-to-noise ratio path with datac13. - - :param bursts: Number of bursts - :type bursts: str - :param frames_per_burst: Number of frames transmitted per burst - :type frames_per_burst: str - :param testframes: Number of test frames to transmit - :type testframes: str - """ - # Facilitate running from main directory as well as inside test/ - rx_side = "freedv_data_raw_rx" - _rxpath = ( - os.path.join("..", "modem") - if os.path.exists(os.path.join("..", "modem")) - else "modem" - ) - _rxpaths = glob.glob(rf"{_rxpath}/**/{rx_side}", recursive=True) - for path in _rxpaths: - rx_side = path - break - - tx_side = "util_tx.py" - if os.path.exists("test") and os.path.exists(os.path.join("test", tx_side)): - tx_side = os.path.join("test", tx_side) - os.environ["PYTHONPATH"] += ":." - - print(f"tx_side={tx_side} / rx_side={rx_side}") - - with subprocess.Popen( - args=[ - "python3", - tx_side, - "--mode", - mode, - "--delay", - "500", - "--framesperburst", - f"{frames_per_burst}", - "--bursts", - f"{bursts}", - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) as transmit: - - with subprocess.Popen( - args=[ - "sox", - "-t", - ".s16", - "-r", - "48000", - "-c", - "1", - "-", - "-t", - ".s16", - "-r", - "8000", - "-c", - "1", - "-", - ], - stdin=transmit.stdout, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) as sox_filter: - - with subprocess.Popen( - args=[ - rx_side, - mode, - "-", - "-", - "--framesperburst", - str(frames_per_burst), - ], - stdin=sox_filter.stdout, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) as receive: - - with subprocess.Popen( - args=[ - "hexdump", - "-C", - ], - stdin=receive.stdout, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) as hexdump: - assert hexdump.stdout - lastline = "".join( - [ - str(line, "UTF-8") - for line in hexdump.stdout.readlines() - if "HELLO" in str(line, "UTF-8") - ] - ) - assert "HELLO WORLD!" in lastline - print(lastline) - - -# @pytest.mark.parametrize("bursts", [BURSTS, 2, 3]) -# @pytest.mark.parametrize("frames_per_burst", [FRAMESPERBURST, 2, 3]) -@pytest.mark.parametrize("bursts", [BURSTS]) -@pytest.mark.parametrize("frames_per_burst", [FRAMESPERBURST]) -@pytest.mark.parametrize("mode", ["datac13", "datac1", "datac3"]) -def test_HighSNR_P_C_DATACx(bursts: int, frames_per_burst: int, mode: str): - proc = multiprocessing.Process( - target=t_HighSNR_P_C_DATACx, - args=[bursts, frames_per_burst, mode], - daemon=True, - ) - - proc.start() - - # Set timeout - timeout = time.time() + 5 - - while time.time() < timeout: - time.sleep(0.1) - - if proc.is_alive(): - proc.terminate() - assert 0, "Timeout waiting for test to complete." - - proc.join() - proc.terminate() - - assert proc.exitcode == 0 - # proc.close() # Python 3.7+ only - proc.terminate() - proc.join() - - -if __name__ == "__main__": - # Run pytest with the current script as the filename. - ecode = pytest.main(["-v", "-s", sys.argv[0]]) - if ecode == 0: - print("errors: 0") - else: - print(ecode) diff --git a/modem/data cemetery/test/test_highsnr_stdio_P_P_datacx.py b/modem/data cemetery/test/test_highsnr_stdio_P_P_datacx.py deleted file mode 100644 index d63aa1e2..00000000 --- a/modem/data cemetery/test/test_highsnr_stdio_P_P_datacx.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Test small multiple-burst messages over a high quality simulated audio channel. - -Legacy test for sending / receiving frames through the codec2 modem -and back through on the other station. Data injection initiates directly into -codec2 API. Tests all three codec2 data frames. - -Can be invoked from CMake, pytest, coverage or directly. - -Uses util_rx.py and util_tx.py in separate processeses to perform -the audio tests. - -@author: N2KIQ -""" - -# pylint: disable=global-statement, invalid-name, unused-import - -import contextlib -import multiprocessing -import os -import subprocess -import sys -import time - -import pytest - -BURSTS = 1 -FRAMESPERBURST = 1 -TESTFRAMES = 3 - -with contextlib.suppress(KeyError): - BURSTS = int(os.environ["BURSTS"]) -with contextlib.suppress(KeyError): - FRAMESPERBURST = int(os.environ["FRAMESPERBURST"]) -with contextlib.suppress(KeyError): - TESTFRAMES = int(os.environ["TESTFRAMES"]) - -# For some reason, sometimes, this test requires the current directory to be `test`. -# Try to adapt dynamically. I still want to figure out why but as a workaround, -# I'm not completely dissatisfied. -if os.path.exists("test"): - os.chdir("test") - - -def t_HighSNR_P_P_DATACx(bursts: int, frames_per_burst: int, mode: str): - """ - Test a high signal-to-noise ratio path with Codec2 modes. - - :param bursts: Number of bursts - :type bursts: str - :param frames_per_burst: Number of frames transmitted per burst - :type frames_per_burst: str - """ - # Facilitate running from main directory as well as inside test/ - tx_side = "util_tx.py" - rx_side = "util_rx.py" - if os.path.exists("test") and os.path.exists(os.path.join("test", tx_side)): - tx_side = os.path.join("test", tx_side) - rx_side = os.path.join("test", rx_side) - os.environ["PYTHONPATH"] += ":." - - print(f"tx_side={tx_side} / rx_side={rx_side}") - - with subprocess.Popen( - args=[ - "python3", - tx_side, - "--mode", - mode, - "--delay", - "500", - "--framesperburst", - str(frames_per_burst), - "--bursts", - str(bursts), - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) as transmit: - - with subprocess.Popen( - args=[ - "python3", - rx_side, - "--mode", - mode, - "--framesperburst", - str(frames_per_burst), - "--bursts", - str(bursts), - "--timeout", - "20", - ], - stdin=transmit.stdout, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) as receive: - assert receive.stdout - lastline = "".join( - [ - str(line, "UTF-8") - for line in receive.stdout.readlines() - if "RECEIVED " in str(line, "UTF-8") - ] - ) - assert f"RECEIVED BURSTS: {bursts}" in lastline - assert f"RECEIVED FRAMES: {frames_per_burst * bursts}" in lastline - assert "RX_ERRORS: 0" in lastline - print(lastline) - - -# @pytest.mark.parametrize("bursts", [BURSTS, 2, 3]) -# @pytest.mark.parametrize("frames_per_burst", [FRAMESPERBURST, 2, 3]) -@pytest.mark.parametrize("bursts", [BURSTS]) -@pytest.mark.parametrize("frames_per_burst", [FRAMESPERBURST]) -@pytest.mark.parametrize("mode", ["datac13", "datac1", "datac3"]) -def test_HighSNR_P_P_DATACx(bursts: int, frames_per_burst: int, mode: str): - proc = multiprocessing.Process( - target=t_HighSNR_P_P_DATACx, - args=[bursts, frames_per_burst, mode], - daemon=True, - ) - - proc.start() - - # Set timeout - timeout = time.time() + 5 - - while time.time() < timeout: - time.sleep(0.1) - - if proc.is_alive(): - proc.terminate() - assert 0, "Timeout waiting for test to complete." - - proc.join() - proc.terminate() - - assert proc.exitcode == 0 - # proc.close() # Python 3.7+ only - proc.terminate() - proc.join() - - -if __name__ == "__main__": - # Run pytest with the current script as the filename. - ecode = pytest.main(["-v", "-s", sys.argv[0]]) - if ecode == 0: - print("errors: 0") - else: - print(ecode) diff --git a/modem/data cemetery/test/test_highsnr_stdio_P_P_multi.py b/modem/data cemetery/test/test_highsnr_stdio_P_P_multi.py deleted file mode 100644 index ebf2bacf..00000000 --- a/modem/data cemetery/test/test_highsnr_stdio_P_P_multi.py +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Test small multiple-burst messages over a high quality simulated audio channel. - -Legacy test for sending / receiving frames through the codec2 modem -and back through on the other station. Data injection initiates directly into -codec2 API. Tests all three codec2 data frames simultaneously. - -Can be invoked from CMake, pytest, coverage or directly. - -Uses util_multimode_tx.py and util_multimode_tx in separate processeses to perform -the audio tests. - -@author: N2KIQ -""" - -# pylint: disable=global-statement, invalid-name, unused-import - -import contextlib -import multiprocessing -import os -import subprocess -import sys -import time - -import pytest - -BURSTS = 1 -FRAMESPERBURST = 1 -TESTFRAMES = 3 - -with contextlib.suppress(KeyError): - BURSTS = int(os.environ["BURSTS"]) -with contextlib.suppress(KeyError): - FRAMESPERBURST = int(os.environ["FRAMESPERBURST"]) -with contextlib.suppress(KeyError): - TESTFRAMES = int(os.environ["TESTFRAMES"]) - -# For some reason, sometimes, this test requires the current directory to be `test`. -# Try to adapt dynamically. I still want to figure out why but as a workaround, -# I'm not completely dissatisfied. -if os.path.exists("test"): - os.chdir("test") - - -def t_HighSNR_P_P_Multi(bursts: int, frames_per_burst: int): - """ - Test a high signal-to-noise ratio path with datac13, DATAC1 and DATAC3. - - :param bursts: Number of bursts - :type bursts: int - :param frames_per_burst: Number of frames transmitted per burst - :type frames_per_burst: int - """ - # Facilitate running from main directory as well as inside test/ - tx_side = "util_multimode_tx.py" - rx_side = "util_multimode_rx.py" - if os.path.exists("test") and os.path.exists(os.path.join("test", tx_side)): - tx_side = os.path.join("test", tx_side) - rx_side = os.path.join("test", rx_side) - os.environ["PYTHONPATH"] += ":." - - print(f"tx_side={tx_side} / rx_side={rx_side}") - - with subprocess.Popen( - args=[ - "python3", - tx_side, - "--delay", - "500", - "--framesperburst", - str(frames_per_burst), - "--bursts", - str(bursts), - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) as transmit: - - with subprocess.Popen( - args=[ - "python3", - rx_side, - "--framesperburst", - str(frames_per_burst), - "--bursts", - str(bursts), - "--timeout", - "20", - ], - stdin=transmit.stdout, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) as receive: - assert receive.stdout - lastline = "".join( - [ - str(line, "UTF-8") - for line in receive.stdout.readlines() - if "DATAC" in str(line, "UTF-8") - ] - ) - assert f"DATAC13: {bursts}/{frames_per_burst * bursts}" in lastline - assert f"DATAC1: {bursts}/{frames_per_burst * bursts}" in lastline - assert f"DATAC3: {bursts}/{frames_per_burst * bursts}" in lastline - print(lastline) - - -# @pytest.mark.parametrize("bursts", [BURSTS, 2, 3]) -# @pytest.mark.parametrize("frames_per_burst", [FRAMESPERBURST, 2, 3]) -@pytest.mark.parametrize("bursts", [BURSTS]) -@pytest.mark.parametrize("frames_per_burst", [FRAMESPERBURST]) -def test_HighSNR_P_P_multi(bursts: int, frames_per_burst: int): - proc = multiprocessing.Process( - target=t_HighSNR_P_P_Multi, - args=[bursts, frames_per_burst], - daemon=True, - ) - - proc.start() - - # Set timeout - timeout = time.time() + 5 - - while time.time() < timeout: - time.sleep(0.1) - - if proc.is_alive(): - proc.terminate() - assert 0, "Timeout waiting for test to complete." - - proc.join() - proc.terminate() - - assert proc.exitcode == 0 - # proc.close() # Python 3.7+ only - proc.terminate() - proc.join() - - -if __name__ == "__main__": - # Run pytest with the current script as the filename. - ecode = pytest.main(["-v", "-s", sys.argv[0]]) - if ecode == 0: - print("errors: 0") - else: - print(ecode) diff --git a/modem/data cemetery/test/test_modem.py b/modem/data cemetery/test/test_modem.py deleted file mode 100755 index ab81ae92..00000000 --- a/modem/data cemetery/test/test_modem.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Test connect frame commands over a high quality simulated audio channel. - -Near end-to-end test for sending / receiving connection control frames through the -Modem and modem and back through on the other station. Data injection initiates from the -queue used by the daemon process into and out of the Modem. - -Can be invoked from CMake, pytest, coverage or directly. - -Uses util_modem_I[RS]S.py in separate process to perform the data transfer. - -@author: DJ2LS, N2KIQ -""" - -import multiprocessing -import os -import sys -import time - -import log_handler -import pytest -import structlog - -try: - import test.util_modem_IRS as irs - import test.util_modem_ISS as iss -except ImportError: - import util_modem_IRS as irs - import util_modem_ISS as iss - - -# This test is currently a little inconsistent. -@pytest.mark.parametrize("command", ["CONNECT"]) -@pytest.mark.flaky(reruns=2) -def test_modem(command, tmp_path): - log_handler.setup_logging(filename=tmp_path / "test_modem", level="INFO") - log = structlog.get_logger("test_modem") - - iss_proc = multiprocessing.Process(target=iss.t_arq_iss, args=[command, tmp_path]) - irs_proc = multiprocessing.Process(target=irs.t_arq_irs, args=[command, tmp_path]) - log.debug("Starting threads.") - iss_proc.start() - irs_proc.start() - - time.sleep(12) - - log.debug("Terminating threads.") - irs_proc.terminate() - iss_proc.terminate() - irs_proc.join() - iss_proc.join() - - for idx in range(2): - try: - os.unlink(tmp_path / f"hfchannel{idx+1}") - except FileNotFoundError as fnfe: - log.debug(f"Unlinking pipe: {fnfe}") - - assert iss_proc.exitcode in [0, -15], f"Transmit side failed test. {iss_proc}" - assert irs_proc.exitcode in [0, -15], f"Receive side failed test. {irs_proc}" - - -if __name__ == "__main__": - # Run pytest with the current script as the filename. - ecode = pytest.main(["-s", "-v", sys.argv[0]]) - if ecode == 0: - print("errors: 0") - else: - print(ecode) diff --git a/modem/data cemetery/test/test_modem_states.py b/modem/data cemetery/test/test_modem_states.py deleted file mode 100644 index 9fd0ae00..00000000 --- a/modem/data cemetery/test/test_modem_states.py +++ /dev/null @@ -1,326 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Test control frame messages over a high quality simulated audio channel. - -Near end-to-end test for sending / receiving select control frames through the -Modem and modem and back through on the other station. Data injection initiates from the -queue used by the daemon process into and out of the Modem. - -Can be invoked from CMake, pytest, coverage or directly. - -Uses util_datac13.py in separate process to perform the data transfer. - -@author: N2KIQ -""" - -import multiprocessing -import numpy as np -import sys -import time - -import pytest - -# pylint: disable=wrong-import-position -sys.path.insert(0, "..") -sys.path.insert(0, "../modem") -import data_handler -import helpers -from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem - - -def print_frame(data: bytearray): - """ - Pretty-print the provided frame. - - :param data: Frame to be output - :type data: bytearray - """ - print(f"Type : {int(data[0])}") - print(f"DXCRC : {bytes(data[1:4])}") - print(f"CallCRC: {bytes(data[4:7])}") - print(f"Call : {helpers.bytes_to_callsign(data[7:13])}") - - -def t_create_frame(frame_type: int, mycall: str, dxcall: str) -> bytearray: - """ - Generate the requested frame. - - :param frame_type: The numerical type of the desired frame. - :type frame_type: int - :param mycall: Callsign of the near station - :type mycall: str - :param dxcall: Callsign of the far station - :type dxcall: str - :return: Bytearray of the requested frame - :rtype: bytearray - """ - mycallsign_bytes = helpers.callsign_to_bytes(mycall) - mycallsign = helpers.bytes_to_callsign(mycallsign_bytes) - mycallsign_crc = helpers.get_crc_24(mycallsign) - - dxcallsign_bytes = helpers.callsign_to_bytes(dxcall) - dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes) - dxcallsign_crc = helpers.get_crc_24(dxcallsign) - - # frame = bytearray(14) - # frame[:1] = bytes([frame_type]) - # frame[1:4] = dxcallsign_crc - # frame[4:7] = mycallsign_crc - # frame[7:13] = mycallsign_bytes - session_id = np.random.bytes(1) - frame = bytearray(14) - frame[:1] = bytes([frame_type]) - frame[1:2] = session_id - frame[2:5] = dxcallsign_crc - frame[5:8] = mycallsign_crc - frame[8:14] = mycallsign_bytes - - return frame - - -def t_create_session_close_old(mycall: str, dxcall: str) -> bytearray: - """ - Generate the session_close frame. - - :param mycall: Callsign of the near station - :type mycall: str - :param dxcall: Callsign of the far station - :type dxcall: str - :return: Bytearray of the requested frame - :rtype: bytearray - """ - return t_create_frame(223, mycall, dxcall) - - -def t_create_session_close(session_id: bytes, dxcall: str) -> bytearray: - """ - Generate the session_close frame. - - :param session_id: Session to close - :type mycall: int - :return: Bytearray of the requested frame - :rtype: bytearray - """ - - dxcallsign_bytes = helpers.callsign_to_bytes(dxcall) - dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes) - dxcallsign_crc = helpers.get_crc_24(dxcallsign) - - # return t_create_frame(223, mycall, dxcall) - frame = bytearray(14) - frame[:1] = bytes([223]) - frame[1:2] = session_id - frame[2:5] = dxcallsign_crc - - return frame - - -def t_create_start_session(mycall: str, dxcall: str) -> bytearray: - """ - Generate the create_session frame. - - :param mycall: Callsign of the near station - :type mycall: str - :param dxcall: Callsign of the far station - :type dxcall: str - :return: Bytearray of the requested frame - :rtype: bytearray - """ - return t_create_frame(221, mycall, dxcall) - - -def t_tsh_dummy(): - """Replacement function for transmit_session_heartbeat""" - print("In transmit_session_heartbeat") - - -def t_foreign_disconnect(mycall: str, dxcall: str): - """ - Execute test to validate that receiving a session open frame sets the correct machine - state. - - :param mycall: Callsign of the near station - :type mycall: str - :param dxcall: Callsign of the far station - :type dxcall: str - :return: Bytearray of the requested frame - :rtype: bytearray - """ - # Set the SSIDs we'll use for this test. - Station.ssid_list = [0, 1, 2, 3, 4] - - # Setup the static parameters for the connection. - mycallsign_bytes = helpers.callsign_to_bytes(mycall) - mycallsign = helpers.bytes_to_callsign(mycallsign_bytes) - Station.mycallsign = mycallsign - Station.mycallsign_crc = helpers.get_crc_24(mycallsign) - - dxcallsign_bytes = helpers.callsign_to_bytes(dxcall) - dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes) - Station.dxcallsign = dxcallsign - Station.dxcallsign_crc = helpers.get_crc_24(dxcallsign) - - # Create the Modem - modem = data_handler.DATA() - modem.arq_cleanup() - - # Replace the heartbeat transmit routine with a No-Op. - modem.transmit_session_heartbeat = t_tsh_dummy - - # Create frame to be 'received' by this station. - create_frame = t_create_start_session(mycall=dxcall, dxcall=mycall) - print_frame(create_frame) - modem.received_session_opener(create_frame) - - assert helpers.callsign_to_bytes(Station.mycallsign) == mycallsign_bytes - assert helpers.callsign_to_bytes(Station.dxcallsign) == dxcallsign_bytes - - assert ARQ.arq_session is True - assert Modem.modem_state == "BUSY" - assert ARQ.arq_session_state == "connecting" - - # Set up a frame from a non-associated station. - # foreigncall_bytes = helpers.callsign_to_bytes("ZZ0ZZ-0") - # foreigncall = helpers.bytes_to_callsign(foreigncall_bytes) - - # close_frame = t_create_session_close_old("ZZ0ZZ-0", "ZZ0ZZ-0") - open_session = create_frame[1:2] - wrong_session = np.random.bytes(1) - while wrong_session == open_session: - wrong_session = np.random.bytes(1) - close_frame = t_create_session_close(wrong_session, dxcall) - print_frame(close_frame) - - # assert ( - # helpers.check_callsign(Station.dxcallsign, bytes(close_frame[4:7]))[0] is False - # ), f"{helpers.get_crc_24(Station.dxcallsign)} == {bytes(close_frame[4:7])} but should be not equal." - - # assert ( - # helpers.check_callsign(foreigncall, bytes(close_frame[4:7]))[0] is True - # ), f"{helpers.get_crc_24(foreigncall)} != {bytes(close_frame[4:7])} but should be equal." - - # Send the non-associated session close frame to the Modem - modem.received_session_close(close_frame) - - assert helpers.callsign_to_bytes(Station.mycallsign) == helpers.callsign_to_bytes( - mycall - ), f"{Station.mycallsign} != {mycall} but should equal." - assert helpers.callsign_to_bytes(Station.dxcallsign) == helpers.callsign_to_bytes( - dxcall - ), f"{Station.dxcallsign} != {dxcall} but should equal." - - assert ARQ.arq_session is True - assert Modem.modem_state == "BUSY" - assert ARQ.arq_session_state == "connecting" - - -def t_valid_disconnect(mycall: str, dxcall: str): - """ - Execute test to validate that receiving a session open frame sets the correct machine - state. - - :param mycall: Callsign of the near station - :type mycall: str - :param dxcall: Callsign of the far station - :type dxcall: str - :return: Bytearray of the requested frame - :rtype: bytearray - """ - # Set the SSIDs we'll use for this test. - Station.ssid_list = [0, 1, 2, 3, 4] - - # Setup the static parameters for the connection. - mycallsign_bytes = helpers.callsign_to_bytes(mycall) - mycallsign = helpers.bytes_to_callsign(mycallsign_bytes) - Station.mycallsign = mycallsign - Station.mycallsign_crc = helpers.get_crc_24(mycallsign) - - dxcallsign_bytes = helpers.callsign_to_bytes(dxcall) - dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes) - Station.dxcallsign = dxcallsign - Station.dxcallsign_crc = helpers.get_crc_24(dxcallsign) - - # Create the Modem - modem = data_handler.DATA() - modem.arq_cleanup() - - # Replace the heartbeat transmit routine with our own, a No-Op. - modem.transmit_session_heartbeat = t_tsh_dummy - - # Create packet to be 'received' by this station. - create_frame = t_create_start_session(mycall=dxcall, dxcall=mycall) - print_frame(create_frame) - modem.received_session_opener(create_frame) - print(ARQ.arq_session) - assert ARQ.arq_session is True - assert Modem.modem_state == "BUSY" - assert ARQ.arq_session_state == "connecting" - - # Create packet to be 'received' by this station. - # close_frame = t_create_session_close_old(mycall=dxcall, dxcall=mycall) - open_session = create_frame[1:2] - print(dxcall) - print("#####################################################") - close_frame = t_create_session_close(open_session, mycall) - print(close_frame[2:5]) - print_frame(close_frame) - modem.received_session_close(close_frame) - - assert helpers.callsign_to_bytes(Station.mycallsign) == mycallsign_bytes - assert helpers.callsign_to_bytes(Station.dxcallsign) == dxcallsign_bytes - - assert ARQ.arq_session is False - assert Modem.modem_state == "IDLE" - assert ARQ.arq_session_state == "disconnected" - - -# These tests are pushed into separate processes as a workaround. These tests -# change the state of one of the static parts of the system. Unfortunately the -# specific state(s) maintained across tests in the same interpreter are not yet known. -# The other tests affected are: `test_modem.py` and the ARQ tests. - - -@pytest.mark.parametrize("mycall", ["AA1AA-2", "DE2DE-0", "E4AWQ-4"]) -@pytest.mark.parametrize("dxcall", ["AA9AA-1", "DE2ED-0", "F6QWE-3"]) -# @pytest.mark.flaky(reruns=2) -def test_foreign_disconnect(mycall: str, dxcall: str): - proc = multiprocessing.Process(target=t_foreign_disconnect, args=(mycall, dxcall)) - # print("Starting threads.") - proc.start() - - time.sleep(5.05) - - # print("Terminating threads.") - proc.terminate() - proc.join() - - # print(f"\nproc.exitcode={proc.exitcode}") - assert proc.exitcode == 0 - - -@pytest.mark.parametrize("mycall", ["AA1AA-2", "DE2DE-0", "M4AWQ-4"]) -@pytest.mark.parametrize("dxcall", ["AA9AA-1", "DE2ED-0", "F6QWE-3"]) -@pytest.mark.flaky(reruns=2) -def test_valid_disconnect(mycall: str, dxcall: str): - proc = multiprocessing.Process(target=t_valid_disconnect, args=(mycall, dxcall)) - # print("Starting threads.") - proc.start() - - time.sleep(5.05) - - # print("Terminating threads.") - proc.terminate() - proc.join() - - # print(f"\nproc.exitcode={proc.exitcode}") - assert proc.exitcode == 0 - - -if __name__ == "__main__": - # Run pytest with the current script as the filename. - ecode = pytest.main(["-v", sys.argv[0]]) - if ecode == 0: - print("errors: 0") - else: - print(ecode) diff --git a/modem/data cemetery/test/test_pa.py b/modem/data cemetery/test/test_pa.py deleted file mode 100644 index f587600b..00000000 --- a/modem/data cemetery/test/test_pa.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# Throw away test program to help understand the care and feeding of PyAudio - -import numpy as np -import pyaudio - -CHUNK = 1024 -FS48 = 48000 -FTEST = 800 -AMP = 16000 - - -def test_pa(): - # 1. play sine wave out of default sound device - - p_audio = pyaudio.PyAudio() - stream = p_audio.open( - format=pyaudio.paInt16, - channels=1, - rate=FS48, - frames_per_buffer=CHUNK, - output=True, - ) - - with open("out48.raw", mode="wb") as f48: - temp = 0 - for _ in range(50): - sine_48k = ( - AMP * np.cos(2 * np.pi * np.arange(temp, temp + CHUNK) * FTEST / FS48) - ).astype(np.int16) - temp += CHUNK - sine_48k.tofile(f48) - stream.write(sine_48k.tobytes()) - sil_48k = np.zeros(CHUNK, dtype=np.int16) - - for _ in range(50): - sil_48k.tofile(f48) - stream.write(sil_48k) - - stream.stop_stream() - stream.close() - p_audio.terminate() - - -if __name__ == "__main__": - test_pa() diff --git a/modem/data cemetery/test/test_resample_48_8.py b/modem/data cemetery/test/test_resample_48_8.py deleted file mode 100644 index a7c07663..00000000 --- a/modem/data cemetery/test/test_resample_48_8.py +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# Unit test for FreeDV API resampler functions, from -# codec2/unittest/t48_8_short.c - generate a sine wave at 8 KHz, -# upsample to 48 kHz, add an interferer, then downsample back to 8 kHz -# -# You can listen to the output files with: -# -# aplay -f S16_LE in8.raw -# aplay -r 48000 -f S16_LE out48.raw -# aplay -f S16_LE out8.raw -# -# They should sound like clean sine waves - -# pylint: disable=global-statement, invalid-name, unused-import - -import os -import sys - -import codec2 -import numpy as np -import pytest - -# dig some constants out -FDMDV_OS_48 = codec2.api.FDMDV_OS_48 -FDMDV_OS_TAPS_48K = codec2.api.FDMDV_OS_TAPS_48K -FDMDV_OS_TAPS_48_8K = codec2.api.FDMDV_OS_TAPS_48_8K - -N8 = 180 # processing buffer size at 8 kHz -N48 = N8 * FDMDV_OS_48 # processing buffer size at 48 kHz -MEM8 = FDMDV_OS_TAPS_48_8K # 8kHz signal filter memory -MEM48 = FDMDV_OS_TAPS_48K # 48kHz signal filter memory -FRAMES = 50 # number of frames to test -FS8 = 8000 -FS48 = 48000 -AMP = 16000 # sine wave amplitude -FTEST8 = 800 # input test frequency at FS=8kHz -FINTER48 = 10000 # interferer frequency at FS=48kHz - -# Due to the design of these resamplers, the processing buffer (at 8kHz) -# must be an integer multiple of oversampling ratio -assert N8 % FDMDV_OS_48 == 0 - - -def test_resampler(): - """ - Test for the codec2 audio resampling routine - """ - # time indexes, we advance every frame - t = 0 - t1 = 0 - - # output files to listen to/evaluate result - with open("in8.raw", mode="wb") as fin8: - with open("out48.raw", mode="wb") as f48: - with open("out8.raw", mode="wb") as fout8: - resampler = codec2.resampler() - - # Generate FRAMES of a sine wave - for _ in range(FRAMES): - # Primary sine wave, which the down-sampling filter should retain. - sine_in8k = ( - AMP * np.cos(2 * np.pi * np.arange(t, t + N8) * FTEST8 / FS8) - ).astype(np.int16) - t += N8 - sine_in8k.tofile(fin8) - - sine_out48k = resampler.resample8_to_48(sine_in8k) - sine_out48k.tofile(f48) - - # Add an interfering sine wave, which the down-sampling filter should (mostly) remove - sine_in48k = ( - sine_out48k - + (AMP / 2) - * np.cos(2 * np.pi * np.arange(t1, t1 + N48) * FINTER48 / FS48) - ).astype(np.int16) - t1 += N48 - - sine_out8k = resampler.resample48_to_8(sine_in48k) - sine_out8k.tofile(fout8) - - # os.unlink("out48.raw") - - # Automated test evaluation -------------------------------------------- - - # The input and output signals will not be time aligned due to the filter - # delays, so compare the magnitude spectrum - - # Read the raw audio files - in8k = np.fromfile("in8.raw", dtype=np.int16) - out8k = np.fromfile("out8.raw", dtype=np.int16) - assert len(in8k) == len(out8k) - # os.unlink("in8.raw") - # os.unlink("out8.raw") - - # Apply hanning filter to raw input data samples - h = np.hanning(len(in8k)) - S1 = np.abs(np.fft.fft(in8k * h)) - S2 = np.abs(np.fft.fft(out8k * h)) - - # Calculate the ratio between signal and noise (error energy). - error = S1 - S2 - error_energy = np.dot(error, error) - ratio = error_energy / np.dot(S1, S1) - ratio_dB = 10 * np.log10(ratio) - - # Establish -40.0 as the noise ratio ceiling - threshdB = -40.0 - print(f"ratio_dB: {ratio_dB:4.2}" % (ratio_dB)) - assert ratio_dB < threshdB - - -if __name__ == "__main__": - # Run pytest with the current script as the filename. - ecode = pytest.main(["-v", sys.argv[0]]) - if ecode == 0: - print("PASS") - else: - print("FAIL") diff --git a/modem/data cemetery/test/test_virtual1.sh b/modem/data cemetery/test/test_virtual1.sh deleted file mode 100755 index d26f9718..00000000 --- a/modem/data cemetery/test/test_virtual1.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -x -# Run a test using the virtual sound cards, sound I/O performed by aplay/arecord at -# Fs=8000 Hz, and we pipe to Python utilities - -function check_alsa_loopback { - lsmod | grep snd_aloop >> /dev/null - if [ $? -eq 1 ]; then - echo "ALSA loopback device not present. Please install with:" - echo - echo " sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT2" - exit 1 - fi -} - -check_alsa_loopback - -RX_LOG=$(mktemp) -MAX_RUN_TIME=2700 - -# make sure all child processes are killed when we exit -trap 'jobs -p | xargs -r kill' EXIT - -arecord --device="plughw:CARD=CHAT2,DEV=0" -r 48000 -f S16_LE -d $MAX_RUN_TIME | python3 util_rx.py --mode datac13 --frames 2 --bursts 5 --debug & -rx_pid=$! -sleep 1 -python3 util_tx.py --mode datac13 --frames 2 --bursts 5 --delay 500 | aplay --device="plughw:CARD=CHAT2,DEV=1" -r 48000 -f S16_LE -wait ${rx_pid} diff --git a/modem/data cemetery/test/test_virtual1a.sh b/modem/data cemetery/test/test_virtual1a.sh deleted file mode 100755 index af528dc9..00000000 --- a/modem/data cemetery/test/test_virtual1a.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -x -# Run a test using the virtual sound cards, tx sound I/O performed by aplay -# and arecord at Fs=48000Hz, we pipe to Python utilities - -MAX_RUN_TIME=2600 - -# make sure all child processes are killed when we exit -trap 'jobs -p | xargs -r kill' EXIT - -arecord -r 48000 --device="plughw:CARD=CHAT1,DEV=0" -f S16_LE -d $MAX_RUN_TIME | \ - python3 util_rx.py --mode datac13 --frames 2 --bursts 5 --debug & -rx_pid=$! -sleep 1 -python3 util_tx.py --mode datac13 --frames 2 --bursts 5 --delay 500 | \ - aplay -r 48000 --device="plughw:CARD=CHAT1,DEV=1" -f S16_LE -wait ${rx_pid} diff --git a/modem/data cemetery/test/test_virtual1b.sh b/modem/data cemetery/test/test_virtual1b.sh deleted file mode 100755 index e38f8e92..00000000 --- a/modem/data cemetery/test/test_virtual1b.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -x -# Run a test using the virtual sound cards, tx sound I/O performed by Python, -# rx using arecord, Fs=48000Hz - -MAX_RUN_TIME=2600 - -# make sure all child processes are killed when we exit -trap 'jobs -p | xargs -r kill' EXIT - -arecord -r 48000 --device="plughw:CARD=CHAT1,DEV=0" -f S16_LE -d $MAX_RUN_TIME | \ - python3 util_rx.py --mode datac13 --frames 2 --bursts 5 --debug --timeout 20 & -rx_pid=$! -sleep 1 -python3 util_tx.py --mode datac13 --frames 2 --bursts 5 --delay 2000 --audiodev -2 -wait ${rx_pid} diff --git a/modem/data cemetery/test/test_virtual1c.sh b/modem/data cemetery/test/test_virtual1c.sh deleted file mode 100755 index c432e08b..00000000 --- a/modem/data cemetery/test/test_virtual1c.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -x -# Run a test using the virtual sound cards, tx sound I/O performed by aplay, -# rx sound I/O by Python, Fs=48000Hz. - -MAX_RUN_TIME=2600 - -# make sure all child processes are killed when we exit -trap 'jobs -p | xargs -r kill' EXIT - -python3 util_rx.py --mode datac13 --frames 2 --bursts 5 --debug --audiodev -2 & -rx_pid=$! -sleep 1 -python3 util_tx.py --mode datac13 --frames 2 --bursts 5 | \ - aplay -r 48000 --device="plughw:CARD=CHAT1,DEV=1" -f S16_LE -wait ${rx_pid} diff --git a/modem/data cemetery/test/test_virtual2.sh b/modem/data cemetery/test/test_virtual2.sh deleted file mode 100755 index 7eec499c..00000000 --- a/modem/data cemetery/test/test_virtual2.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -x -# Run a test using the virtual sound cards, Python audio I/O - -function check_alsa_loopback { - lsmod | grep snd_aloop >> /dev/null - if [ $? -eq 1 ]; then - echo "ALSA loopback device not present. Please install with:" - echo - echo " sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT2" - exit 1 - fi -} - -check_alsa_loopback - -# make sure all child processes are killed when we exit -trap 'jobs -p | xargs -r kill' EXIT - -python3 util_callback_rx.py --mode datac13 --frames 2 --bursts 3 --audiodev -2 --debug & -rx_pid=$! -sleep 1 -python3 util_tx.py --mode datac13 --frames 2 --bursts 3 --audiodev -2 -wait ${rx_pid} diff --git a/modem/data cemetery/test/test_virtual3a.sh b/modem/data cemetery/test/test_virtual3a.sh deleted file mode 100755 index e49f978f..00000000 --- a/modem/data cemetery/test/test_virtual3a.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -x -# Run a test using the virtual sound cards, Python audio I/O - -function check_alsa_loopback { - lsmod | grep snd_aloop >> /dev/null - if [ $? -eq 1 ]; then - echo "ALSA loopback device not present. Please install with:" - echo - echo " sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT2" - exit 1 - fi -} - -check_alsa_loopback - -# make sure all child processes are killed when we exit -trap 'jobs -p | xargs -r kill' EXIT - -python3 util_callback_rx.py --mode datac13 --frames 2 --bursts 3 --audiodev -2 --debug & -rx_pid=$! -#sleep 1 -python3 util_tx.py --mode datac13 --frames 2 --bursts 3 --audiodev -2 -wait ${rx_pid} diff --git a/modem/data cemetery/test/test_virtual3b.sh b/modem/data cemetery/test/test_virtual3b.sh deleted file mode 100755 index e6fcc5a1..00000000 --- a/modem/data cemetery/test/test_virtual3b.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -x -# Run a test using the virtual sound cards, Python audio I/O - -function check_alsa_loopback { - lsmod | grep snd_aloop >> /dev/null - if [ $? -eq 1 ]; then - echo "ALSA loopback device not present. Please install with:" - echo - echo " sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT2" - exit 1 - fi -} - -check_alsa_loopback - -# make sure all child processes are killed when we exit -trap 'jobs -p | xargs -r kill' EXIT - -python3 util_callback_rx_outside.py --mode datac13 --frames 2 --bursts 3 --audiodev -2 --debug & -rx_pid=$! -#sleep 1 -python3 util_tx.py --mode datac13 --frames 2 --bursts 3 --audiodev -2 -wait ${rx_pid} diff --git a/modem/data cemetery/test/test_virtual4a.sh b/modem/data cemetery/test/test_virtual4a.sh deleted file mode 100755 index de6ce4ff..00000000 --- a/modem/data cemetery/test/test_virtual4a.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -x -# Run a test using the virtual sound cards - -function check_alsa_loopback { - lsmod | grep snd_aloop >> /dev/null - if [ $? -eq 1 ]; then - echo "ALSA loopback device not present. Please install with:" - echo - echo " sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT2" - exit 1 - fi -} - -myInterruptHandler() -{ - exit 1 -} - -check_alsa_loopback - -RX_LOG=$(mktemp) - -trap myInterruptHandler SIGINT - -# make sure all child processes are killed when we exit -trap 'jobs -p | xargs -r kill' EXIT - -python3 util_callback_multimode_rx.py --timeout 60 --framesperburst 2 --bursts 2 --audiodev -2 --debug & -rx_pid=$! -sleep 1 -python3 util_multimode_tx.py --framesperburst 2 --bursts 2 --audiodev -2 --delay 500 -wait ${rx_pid} diff --git a/modem/data cemetery/test/test_virtual4b.sh b/modem/data cemetery/test/test_virtual4b.sh deleted file mode 100755 index dcb0190b..00000000 --- a/modem/data cemetery/test/test_virtual4b.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -x -# Run a test using the virtual sound cards - -function check_alsa_loopback { - lsmod | grep snd_aloop >> /dev/null - if [ $? -eq 1 ]; then - echo "ALSA loopback device not present. Please install with:" - echo - echo " sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT2" - exit 1 - fi -} - -myInterruptHandler() -{ - exit 1 -} - -check_alsa_loopback - -RX_LOG=$(mktemp) - -trap myInterruptHandler SIGINT - -# make sure all child processes are killed when we exit -trap 'jobs -p | xargs -r kill' EXIT - -python3 util_callback_multimode_rx_outside.py --timeout 60 --framesperburst 2 --bursts 2 --audiodev -2 --debug & -rx_pid=$! -sleep 1 -python3 util_multimode_tx.py --framesperburst 2 --bursts 2 --audiodev -2 --delay 500 -wait ${rx_pid} diff --git a/modem/data cemetery/test/test_virtual_mm.sh b/modem/data cemetery/test/test_virtual_mm.sh deleted file mode 100755 index 9325aa74..00000000 --- a/modem/data cemetery/test/test_virtual_mm.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -x -# Run a test using the virtual sound cards - -function check_alsa_loopback { - lsmod | grep snd_aloop >> /dev/null - if [ $? -eq 1 ]; then - echo "ALSA loopback device not present. Please install with:" - echo - echo " sudo modprobe snd-aloop index=1,2 enable=1,1 pcm_substreams=1,1 id=CHAT1,CHAT2" - exit 1 - fi -} - -myInterruptHandler() -{ - exit 1 -} - -check_alsa_loopback - -RX_LOG=$(mktemp) - -trap myInterruptHandler SIGINT - -# make sure all child processes are killed when we exit -trap 'jobs -p | xargs -r kill' EXIT - -python3 util_multimode_rx.py --timeout 60 --framesperburst 2 --bursts 2 --audiodev -2 --debug & -rx_pid=$! -sleep 1 -python3 util_multimode_tx.py --framesperburst 2 --bursts 2 --audiodev -2 --delay 500 -wait ${rx_pid} diff --git a/modem/data cemetery/test/util_callback_multimode_rx.py b/modem/data cemetery/test/util_callback_multimode_rx.py deleted file mode 100644 index 94eda986..00000000 --- a/modem/data cemetery/test/util_callback_multimode_rx.py +++ /dev/null @@ -1,323 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Wed Dec 23 07:04:24 2020 - -@author: DJ2LS -""" - -import argparse -import ctypes -import sys -import threading -import time - -import numpy as np -import pyaudio - -sys.path.insert(0, "..") -from modem import codec2 - -# --------------------------------------------GET PARAMETER INPUTS -parser = argparse.ArgumentParser(description="FreeDATA audio test") -parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int) -parser.add_argument("--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int) -parser.add_argument( - "--audiodev", - dest="AUDIO_INPUT_DEVICE", - default=-1, - type=int, - help="audio device number to use", -) -parser.add_argument("--debug", dest="DEBUGGING_MODE", action="store_true") -parser.add_argument( - "--list", - dest="LIST", - action="store_true", - help="list audio devices by number and exit", -) -parser.add_argument( - "--timeout", - dest="TIMEOUT", - default=10, - type=int, - help="Timeout (seconds) before test ends", -) - -args, _ = parser.parse_known_args() - -if args.LIST: - p = pyaudio.PyAudio() - for dev in range(p.get_device_count()): - print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) - sys.exit() - - -class Test: - def __init__(self): - self.N_BURSTS = args.N_BURSTS - self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST - self.AUDIO_INPUT_DEVICE = args.AUDIO_INPUT_DEVICE - self.DEBUGGING_MODE = args.DEBUGGING_MODE - self.TIMEOUT = args.TIMEOUT - - # AUDIO PARAMETERS - # v-- consider increasing if you get nread_exceptions > 0 - self.AUDIO_FRAMES_PER_BUFFER = 2400 * 2 - self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 - self.AUDIO_SAMPLE_RATE_RX = 48000 - - # make sure our resampler will work - assert ( - self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE - ) == codec2.api.FDMDV_OS_48 - - # check if we want to use an audio device then do a pyaudio init - if self.AUDIO_INPUT_DEVICE != -1: - self.p = pyaudio.PyAudio() - # auto search for loopback devices - if self.AUDIO_INPUT_DEVICE == -2: - loopback_list = [ - dev - for dev in range(self.p.get_device_count()) - if "Loopback: PCM" - in self.p.get_device_info_by_index(dev)["name"] - ] - if len(loopback_list) >= 2: - # 0 = RX 1 = TX - self.AUDIO_INPUT_DEVICE = loopback_list[0] - print(f"loopback_list rx: {loopback_list}", file=sys.stderr) - else: - sys.exit() - - print( - f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \ - AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}", - file=sys.stderr, - ) - - self.stream_rx = self.p.open( - format=pyaudio.paInt16, - channels=1, - rate=self.AUDIO_SAMPLE_RATE_RX, - frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER, - input=True, - output=False, - input_device_index=self.AUDIO_INPUT_DEVICE, - stream_callback=self.callback, - ) - else: - print("test_callback_multimode_rx: Not written for STDIN usage.") - print("Exiting.") - sys.exit() - - # open codec2 instance - self.datac13_freedv = ctypes.cast( - codec2.api.freedv_open(codec2.FREEDV_MODE.datac13.value), ctypes.c_void_p - ) - self.datac13_bytes_per_frame = int( - codec2.api.freedv_get_bits_per_modem_frame(self.datac13_freedv) / 8 - ) - self.datac13_bytes_out = ctypes.create_string_buffer(self.datac13_bytes_per_frame) - codec2.api.freedv_set_frames_per_burst( - self.datac13_freedv, self.N_FRAMES_PER_BURST - ) - self.datac13_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER) - - self.datac1_freedv = ctypes.cast( - codec2.api.freedv_open(codec2.FREEDV_MODE.datac1.value), ctypes.c_void_p - ) - self.datac1_bytes_per_frame = int( - codec2.api.freedv_get_bits_per_modem_frame(self.datac1_freedv) / 8 - ) - self.datac1_bytes_out = ctypes.create_string_buffer(self.datac1_bytes_per_frame) - codec2.api.freedv_set_frames_per_burst( - self.datac1_freedv, self.N_FRAMES_PER_BURST - ) - self.datac1_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER) - - self.datac3_freedv = ctypes.cast( - codec2.api.freedv_open(codec2.FREEDV_MODE.datac3.value), ctypes.c_void_p - ) - self.datac3_bytes_per_frame = int( - codec2.api.freedv_get_bits_per_modem_frame(self.datac3_freedv) / 8 - ) - self.datac3_bytes_out = ctypes.create_string_buffer(self.datac3_bytes_per_frame) - codec2.api.freedv_set_frames_per_burst( - self.datac3_freedv, self.N_FRAMES_PER_BURST - ) - self.datac3_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER) - - # SET COUNTERS - self.rx_total_frames_datac13 = 0 - self.rx_frames_datac13 = 0 - self.rx_bursts_datac13 = 0 - - self.rx_total_frames_datac1 = 0 - self.rx_frames_datac1 = 0 - self.rx_bursts_datac1 = 0 - - self.rx_total_frames_datac3 = 0 - self.rx_frames_datac3 = 0 - self.rx_bursts_datac3 = 0 - - self.rx_errors = 0 - self.nread_exceptions = 0 - self.timeout = time.time() + self.TIMEOUT - self.receive = True - self.resampler = codec2.resampler() - - # Copy received 48 kHz to a file. Listen to this file with: - # aplay -r 48000 -f S16_LE rx48_callback.raw - # Corruption of this file is a good way to detect audio card issues - self.frx = open("rx48_callback_multimode.raw", mode="wb") - - # initial nin values - self.datac13_nin = codec2.api.freedv_nin(self.datac13_freedv) - self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv) - self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv) - - self.LOGGER_THREAD = threading.Thread( - target=self.print_stats, name="LOGGER_THREAD" - ) - self.LOGGER_THREAD.start() - - def callback(self, data_in48k, frame_count, time_info, status): - x = np.frombuffer(data_in48k, dtype=np.int16) - x.tofile(self.frx) - x = self.resampler.resample48_to_8(x) - - self.datac13_buffer.push(x) - self.datac1_buffer.push(x) - self.datac3_buffer.push(x) - - while self.datac13_buffer.nbuffer >= self.datac13_nin: - # demodulate audio - nbytes = codec2.api.freedv_rawdatarx( - self.datac13_freedv, - self.datac13_bytes_out, - self.datac13_buffer.buffer.ctypes, - ) - self.datac13_buffer.pop(self.datac13_nin) - self.datac13_nin = codec2.api.freedv_nin(self.datac13_freedv) - if nbytes == self.datac13_bytes_per_frame: - self.rx_total_frames_datac13 = self.rx_total_frames_datac13 + 1 - self.rx_frames_datac13 = self.rx_frames_datac13 + 1 - - if self.rx_frames_datac13 == self.N_FRAMES_PER_BURST: - self.rx_frames_datac13 = 0 - self.rx_bursts_datac13 = self.rx_bursts_datac13 + 1 - - while self.datac1_buffer.nbuffer >= self.datac1_nin: - # demodulate audio - nbytes = codec2.api.freedv_rawdatarx( - self.datac1_freedv, - self.datac1_bytes_out, - self.datac1_buffer.buffer.ctypes, - ) - self.datac1_buffer.pop(self.datac1_nin) - self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv) - if nbytes == self.datac1_bytes_per_frame: - self.rx_total_frames_datac1 = self.rx_total_frames_datac1 + 1 - self.rx_frames_datac1 = self.rx_frames_datac1 + 1 - - if self.rx_frames_datac1 == self.N_FRAMES_PER_BURST: - self.rx_frames_datac1 = 0 - self.rx_bursts_datac1 = self.rx_bursts_datac1 + 1 - - while self.datac3_buffer.nbuffer >= self.datac3_nin: - # demodulate audio - nbytes = codec2.api.freedv_rawdatarx( - self.datac3_freedv, - self.datac3_bytes_out, - self.datac3_buffer.buffer.ctypes, - ) - self.datac3_buffer.pop(self.datac3_nin) - self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv) - if nbytes == self.datac3_bytes_per_frame: - self.rx_total_frames_datac3 = self.rx_total_frames_datac3 + 1 - self.rx_frames_datac3 = self.rx_frames_datac3 + 1 - - if self.rx_frames_datac3 == self.N_FRAMES_PER_BURST: - self.rx_frames_datac3 = 0 - self.rx_bursts_datac3 = self.rx_bursts_datac3 + 1 - - if ( - self.rx_bursts_datac13 and self.rx_bursts_datac1 and self.rx_bursts_datac3 - ) == self.N_BURSTS: - self.receive = False - - return (None, pyaudio.paContinue) - - def print_stats(self): - while self.receive: - time.sleep(0.01) - if self.DEBUGGING_MODE: - self.datac13_rxstatus = codec2.api.freedv_get_rx_status( - self.datac13_freedv - ) - self.datac13_rxstatus = codec2.api.rx_sync_flags_to_text[ - self.datac13_rxstatus - ] - - self.datac1_rxstatus = codec2.api.freedv_get_rx_status( - self.datac1_freedv - ) - self.datac1_rxstatus = codec2.api.rx_sync_flags_to_text[ - self.datac1_rxstatus - ] - - self.datac3_rxstatus = codec2.api.freedv_get_rx_status( - self.datac3_freedv - ) - self.datac3_rxstatus = codec2.api.rx_sync_flags_to_text[ - self.datac3_rxstatus - ] - - print( - "NIN0: %5d RX_STATUS0: %4s NIN1: %5d RX_STATUS1: %4s NIN3: %5d RX_STATUS3: %4s" - % ( - self.datac13_nin, - self.datac13_rxstatus, - self.datac1_nin, - self.datac1_rxstatus, - self.datac3_nin, - self.datac3_rxstatus, - ), - file=sys.stderr, - ) - - def run_audio(self): - try: - print("starting pyaudio callback", file=sys.stderr) - self.stream_rx.start_stream() - except Exception as e: - print(f"pyAudio error: {e}", file=sys.stderr) - - while self.receive and time.time() < self.timeout: - time.sleep(1) - - if time.time() >= self.timeout and self.stream_rx.is_active(): - print("TIMEOUT REACHED") - self.receive = False - - if self.nread_exceptions: - print( - "nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." - % self.nread_exceptions, - file=sys.stderr, - ) - - print( - f"datac13: {self.rx_bursts_datac13}/{self.rx_total_frames_datac13} DATAC1: {self.rx_bursts_datac1}/{self.rx_total_frames_datac1} DATAC3: {self.rx_bursts_datac3}/{self.rx_total_frames_datac3}", - file=sys.stderr, - ) - self.frx.close() - - # cloese pyaudio instance - self.stream_rx.close() - self.p.terminate() - - -test = Test() -test.run_audio() diff --git a/modem/data cemetery/test/util_callback_multimode_rx_outside.py b/modem/data cemetery/test/util_callback_multimode_rx_outside.py deleted file mode 100644 index 1bef5a51..00000000 --- a/modem/data cemetery/test/util_callback_multimode_rx_outside.py +++ /dev/null @@ -1,311 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Wed Dec 23 07:04:24 2020 - -@author: DJ2LS -""" - -import argparse -import ctypes -import sys -import time - -import numpy as np -import pyaudio - -sys.path.insert(0, "..") -from modem import codec2 - -# --------------------------------------------GET PARAMETER INPUTS -parser = argparse.ArgumentParser(description="FreeDATA audio test") -parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int) -parser.add_argument("--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int) -parser.add_argument( - "--audiodev", - dest="AUDIO_INPUT_DEVICE", - default=-1, - type=int, - help="audio device number to use", -) -parser.add_argument("--debug", dest="DEBUGGING_MODE", action="store_true") -parser.add_argument( - "--list", - dest="LIST", - action="store_true", - help="list audio devices by number and exit", -) -parser.add_argument( - "--timeout", - dest="TIMEOUT", - default=10, - type=int, - help="Timeout (seconds) before test ends", -) - -args, _ = parser.parse_known_args() - -if args.LIST: - p = pyaudio.PyAudio() - for dev in range(p.get_device_count()): - print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) - - -class Test: - def __init__(self): - self.N_BURSTS = args.N_BURSTS - self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST - self.AUDIO_INPUT_DEVICE = args.AUDIO_INPUT_DEVICE - self.DEBUGGING_MODE = args.DEBUGGING_MODE - self.TIMEOUT = args.TIMEOUT - - # AUDIO PARAMETERS - self.AUDIO_FRAMES_PER_BUFFER = ( - 2400 * 2 - ) # <- consider increasing if you get nread_exceptions > 0 - self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 - self.AUDIO_SAMPLE_RATE_RX = 48000 - - # make sure our resampler will work - assert ( - self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE - ) == codec2.api.FDMDV_OS_48 - - # check if we want to use an audio device then do a pyaudio init - if self.AUDIO_INPUT_DEVICE != -1: - self.p = pyaudio.PyAudio() - # auto search for loopback devices - if self.AUDIO_INPUT_DEVICE == -2: - loopback_list = [ - dev - for dev in range(self.p.get_device_count()) - if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"] - ] - - if len(loopback_list) >= 2: - self.AUDIO_INPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX - print(f"loopback_list rx: {loopback_list}", file=sys.stderr) - else: - sys.exit() - - print( - f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \ - AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}", - file=sys.stderr, - ) - - self.stream_rx = self.p.open( - format=pyaudio.paInt16, - channels=1, - rate=self.AUDIO_SAMPLE_RATE_RX, - frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER, - input=True, - output=False, - input_device_index=self.AUDIO_INPUT_DEVICE, - stream_callback=self.callback, - ) - else: - print("test_callback_multimode_rx_outside: Not written for STDIN usage.") - print("Exiting.") - sys.exit() - - # open codec2 instance - self.datac13_freedv = ctypes.cast( - codec2.api.freedv_open(codec2.FREEDV_MODE.datac13.value), ctypes.c_void_p - ) - self.datac13_bytes_per_frame = int( - codec2.api.freedv_get_bits_per_modem_frame(self.datac13_freedv) / 8 - ) - self.datac13_bytes_out = ctypes.create_string_buffer(self.datac13_bytes_per_frame) - codec2.api.freedv_set_frames_per_burst( - self.datac13_freedv, self.N_FRAMES_PER_BURST - ) - self.datac13_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER) - - self.datac1_freedv = ctypes.cast( - codec2.api.freedv_open(codec2.FREEDV_MODE.datac1.value), ctypes.c_void_p - ) - self.datac1_bytes_per_frame = int( - codec2.api.freedv_get_bits_per_modem_frame(self.datac1_freedv) / 8 - ) - self.datac1_bytes_out = ctypes.create_string_buffer(self.datac1_bytes_per_frame) - codec2.api.freedv_set_frames_per_burst( - self.datac1_freedv, self.N_FRAMES_PER_BURST - ) - self.datac1_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER) - - self.datac3_freedv = ctypes.cast( - codec2.api.freedv_open(codec2.FREEDV_MODE.datac3.value), ctypes.c_void_p - ) - self.datac3_bytes_per_frame = int( - codec2.api.freedv_get_bits_per_modem_frame(self.datac3_freedv) / 8 - ) - self.datac3_bytes_out = ctypes.create_string_buffer(self.datac3_bytes_per_frame) - codec2.api.freedv_set_frames_per_burst( - self.datac3_freedv, self.N_FRAMES_PER_BURST - ) - self.datac3_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER) - - # SET COUNTERS - self.rx_total_frames_datac13 = 0 - self.rx_frames_datac13 = 0 - self.rx_bursts_datac13 = 0 - - self.rx_total_frames_datac1 = 0 - self.rx_frames_datac1 = 0 - self.rx_bursts_datac1 = 0 - - self.rx_total_frames_datac3 = 0 - self.rx_frames_datac3 = 0 - self.rx_bursts_datac3 = 0 - - self.rx_errors = 0 - self.nread_exceptions = 0 - self.timeout = time.time() + self.TIMEOUT - self.receive = True - self.resampler = codec2.resampler() - - # Copy received 48 kHz to a file. Listen to this file with: - # aplay -r 48000 -f S16_LE rx48_callback.raw - # Corruption of this file is a good way to detect audio card issues - self.frx = open("rx48_callback_multimode.raw", mode="wb") - - # initial nin values - self.datac13_nin = codec2.api.freedv_nin(self.datac13_freedv) - self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv) - self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv) - - def callback(self, data_in48k, frame_count, time_info, status): - - x = np.frombuffer(data_in48k, dtype=np.int16) - x.tofile(self.frx) - x = self.resampler.resample48_to_8(x) - - self.datac13_buffer.push(x) - self.datac1_buffer.push(x) - self.datac3_buffer.push(x) - - return (None, pyaudio.paContinue) - - def print_stats(self): - if self.DEBUGGING_MODE: - self.datac13_rxstatus = codec2.api.freedv_get_rx_status(self.datac13_freedv) - self.datac13_rxstatus = codec2.api.rx_sync_flags_to_text[ - self.datac13_rxstatus - ] - - self.datac1_rxstatus = codec2.api.freedv_get_rx_status(self.datac1_freedv) - self.datac1_rxstatus = codec2.api.rx_sync_flags_to_text[ - self.datac1_rxstatus - ] - - self.datac3_rxstatus = codec2.api.freedv_get_rx_status(self.datac3_freedv) - self.datac3_rxstatus = codec2.api.rx_sync_flags_to_text[ - self.datac3_rxstatus - ] - - print( - "NIN0: %5d RX_STATUS0: %4s NIN1: %5d RX_STATUS1: %4s NIN3: %5d RX_STATUS3: %4s" - % ( - self.datac13_nin, - self.datac13_rxstatus, - self.datac1_nin, - self.datac1_rxstatus, - self.datac3_nin, - self.datac3_rxstatus, - ), - file=sys.stderr, - ) - - def run_audio(self): - try: - print("starting pyaudio callback", file=sys.stderr) - self.stream_rx.start_stream() - except Exception as e: - print(f"pyAudio error: {e}", file=sys.stderr) - - while self.receive and time.time() < self.timeout: - while self.datac13_buffer.nbuffer >= self.datac13_nin: - # demodulate audio - nbytes = codec2.api.freedv_rawdatarx( - self.datac13_freedv, - self.datac13_bytes_out, - self.datac13_buffer.buffer.ctypes, - ) - self.datac13_buffer.pop(self.datac13_nin) - self.datac13_nin = codec2.api.freedv_nin(self.datac13_freedv) - if nbytes == self.datac13_bytes_per_frame: - self.rx_total_frames_datac13 = self.rx_total_frames_datac13 + 1 - self.rx_frames_datac13 = self.rx_frames_datac13 + 1 - - if self.rx_frames_datac13 == self.N_FRAMES_PER_BURST: - self.rx_frames_datac13 = 0 - self.rx_bursts_datac13 = self.rx_bursts_datac13 + 1 - self.print_stats() - - while self.datac1_buffer.nbuffer >= self.datac1_nin: - # demodulate audio - nbytes = codec2.api.freedv_rawdatarx( - self.datac1_freedv, - self.datac1_bytes_out, - self.datac1_buffer.buffer.ctypes, - ) - self.datac1_buffer.pop(self.datac1_nin) - self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv) - if nbytes == self.datac1_bytes_per_frame: - self.rx_total_frames_datac1 = self.rx_total_frames_datac1 + 1 - self.rx_frames_datac1 = self.rx_frames_datac1 + 1 - - if self.rx_frames_datac1 == self.N_FRAMES_PER_BURST: - self.rx_frames_datac1 = 0 - self.rx_bursts_datac1 = self.rx_bursts_datac1 + 1 - self.print_stats() - - while self.datac3_buffer.nbuffer >= self.datac3_nin: - # demodulate audio - nbytes = codec2.api.freedv_rawdatarx( - self.datac3_freedv, - self.datac3_bytes_out, - self.datac3_buffer.buffer.ctypes, - ) - self.datac3_buffer.pop(self.datac3_nin) - self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv) - if nbytes == self.datac3_bytes_per_frame: - self.rx_total_frames_datac3 = self.rx_total_frames_datac3 + 1 - self.rx_frames_datac3 = self.rx_frames_datac3 + 1 - - if self.rx_frames_datac3 == self.N_FRAMES_PER_BURST: - self.rx_frames_datac3 = 0 - self.rx_bursts_datac3 = self.rx_bursts_datac3 + 1 - self.print_stats() - - if ( - self.rx_bursts_datac13 - and self.rx_bursts_datac1 - and self.rx_bursts_datac3 - ) == self.N_BURSTS: - self.receive = False - - if time.time() >= self.timeout: - print("TIMEOUT REACHED") - - if self.nread_exceptions: - print( - "nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." - % self.nread_exceptions, - file=sys.stderr, - ) - - print( - f"datac13: {self.rx_bursts_datac13}/{self.rx_total_frames_datac13} DATAC1: {self.rx_bursts_datac1}/{self.rx_total_frames_datac1} DATAC3: {self.rx_bursts_datac3}/{self.rx_total_frames_datac3}", - file=sys.stderr, - ) - self.frx.close() - - # cloese pyaudio instance - self.stream_rx.close() - self.p.terminate() - - -test = Test() -test.run_audio() diff --git a/modem/data cemetery/test/util_callback_multimode_tx.py b/modem/data cemetery/test/util_callback_multimode_tx.py deleted file mode 100644 index c155c366..00000000 --- a/modem/data cemetery/test/util_callback_multimode_tx.py +++ /dev/null @@ -1,256 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Wed Dec 23 07:04:24 2020 - -@author: DJ2LS -""" - -import argparse -import ctypes -import queue -import sys -import time - -import numpy as np -import pyaudio - -sys.path.insert(0, "..") -from modem import codec2 - -# --------------------------------------------GET PARAMETER INPUTS -parser = argparse.ArgumentParser(description="FreeDATA audio test") -parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int) -parser.add_argument("--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int) -parser.add_argument("--delay", dest="DELAY_BETWEEN_BURSTS", default=500, type=int) -parser.add_argument( - "--audiodev", - dest="AUDIO_OUTPUT_DEVICE", - default=-1, - type=int, - help="audio output device number to use", -) -parser.add_argument( - "--list", - dest="LIST", - action="store_true", - help="list audio devices by number and exit", -) -parser.add_argument( - "--testframes", - dest="TESTFRAMES", - action="store_true", - default=False, - help="generate testframes", -) - -args, _ = parser.parse_known_args() - -if args.LIST: - p = pyaudio.PyAudio() - for dev in range(p.get_device_count()): - print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) - sys.exit() - - -class Test: - def __init__(self): - - self.dataqueue = queue.Queue() - self.N_BURSTS = args.N_BURSTS - self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST - self.AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE - self.DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS / 1000 - - # AUDIO PARAMETERS - # v-- consider increasing if you get nread_exceptions > 0 - self.AUDIO_FRAMES_PER_BUFFER = 2400 - self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 - self.AUDIO_SAMPLE_RATE_TX = 48000 - - # make sure our resampler will work - assert ( - self.AUDIO_SAMPLE_RATE_TX / self.MODEM_SAMPLE_RATE - ) == codec2.api.FDMDV_OS_48 - - self.transmit = True - - self.resampler = codec2.resampler() - - # check if we want to use an audio device then do a pyaudio init - if self.AUDIO_OUTPUT_DEVICE != -1: - self.p = pyaudio.PyAudio() - # auto search for loopback devices - if self.AUDIO_OUTPUT_DEVICE == -2: - loopback_list = [ - dev - for dev in range(self.p.get_device_count()) - if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"] - ] - - if len(loopback_list) >= 2: - self.AUDIO_OUTPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX - print(f"loopback_list rx: {loopback_list}", file=sys.stderr) - else: - sys.exit() - - print( - f"AUDIO OUTPUT DEVICE: {self.AUDIO_OUTPUT_DEVICE} " - f"DEVICE: {self.p.get_device_info_by_index(self.AUDIO_OUTPUT_DEVICE)['name']} " - f"AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_TX}", - file=sys.stderr, - ) - - self.stream_tx = self.p.open( - format=pyaudio.paInt16, - channels=1, - rate=self.AUDIO_SAMPLE_RATE_TX, - frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER, - input=False, - output=True, - output_device_index=self.AUDIO_OUTPUT_DEVICE, - stream_callback=self.callback, - ) - else: - print("test_callback_multimode_tx: Not written for STDOUT usage.") - print("Exiting.") - sys.exit() - - # Copy received 48 kHz to a file. Listen to this file with: - # aplay -r 48000 -f S16_LE rx48_callback.raw - # Corruption of this file is a good way to detect audio card issues - self.ftx = open("tx48_callback.raw", mode="wb") - - # data binary string - if args.TESTFRAMES: - self.data_out = bytearray(14) - self.data_out[:1] = bytes([255]) - self.data_out[1:2] = bytes([1]) - self.data_out[2:] = b"HELLO WORLD" - - else: - self.data_out = b"HELLO WORLD!" - - def callback(self, data_in48k, frame_count, time_info, status): - - data_out48k = self.dataqueue.get() - return (data_out48k, pyaudio.paContinue) - - def run_audio(self): - try: - print("starting pyaudio callback", file=sys.stderr) - self.stream_tx.start_stream() - except Exception as e: - print(f"pyAudio error: {e}", file=sys.stderr) - - sheeps = 0 - while self.transmit: - time.sleep(1) - sheeps = sheeps + 1 - print(f"counting sheeps...{sheeps}") - - self.ftx.close() - - # close pyaudio instance - self.stream_tx.close() - self.p.terminate() - - def create_modulation(self): - - modes = [ - codec2.FREEDV_MODE.datac13.value, - codec2.FREEDV_MODE.datac1.value, - codec2.FREEDV_MODE.datac3.value, - ] - for m in modes: - - freedv = ctypes.cast(codec2.api.freedv_open(m), ctypes.c_void_p) - - n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv) - mod_out = ctypes.create_string_buffer(2 * n_tx_modem_samples) - - n_tx_preamble_modem_samples = ( - codec2.api.freedv_get_n_tx_preamble_modem_samples(freedv) - ) - mod_out_preamble = ctypes.create_string_buffer( - 2 * n_tx_preamble_modem_samples - ) - - n_tx_postamble_modem_samples = ( - codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv) - ) - mod_out_postamble = ctypes.create_string_buffer( - 2 * n_tx_postamble_modem_samples - ) - - bytes_per_frame = int( - codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8 - ) - payload_per_frame = bytes_per_frame - 2 - - buffer = bytearray(payload_per_frame) - # set buffer size to length of data which will be sent - buffer[: len(self.data_out)] = self.data_out - - crc = ctypes.c_ushort( - codec2.api.freedv_gen_crc16(bytes(buffer), payload_per_frame) - ) # generate CRC16 - # convert crc to 2 byte hex string - crc = crc.value.to_bytes(2, byteorder="big") - buffer += crc # append crc16 to buffer - data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) - - for i in range(1, self.N_BURSTS + 1): - - # write preamble to txbuffer - codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble) - txbuffer = bytes(mod_out_preamble) - - # create modulaton for N = FRAMESPERBURST and append it to txbuffer - for n in range(1, self.N_FRAMES_PER_BURST + 1): - - 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) - print( - f"GENERATING TX BURST: {i}/{self.N_BURSTS} FRAME: {n}/{self.N_FRAMES_PER_BURST}", - file=sys.stderr, - ) - - # append postamble to txbuffer - codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble) - txbuffer += bytes(mod_out_postamble) - - # append a delay between bursts as audio silence - samples_delay = int(self.MODEM_SAMPLE_RATE * self.DELAY_BETWEEN_BURSTS) - mod_out_silence = ctypes.create_string_buffer(samples_delay * 2) - txbuffer += bytes(mod_out_silence) - - # resample up to 48k (resampler works on np.int16) - x = np.frombuffer(txbuffer, dtype=np.int16) - txbuffer_48k = self.resampler.resample8_to_48(x) - - # split modulated audio to chunks - # https://newbedev.com/how-to-split-a-byte-string-into-separate-bytes-in-python - txbuffer_48k = bytes(txbuffer_48k) - chunk = [ - txbuffer_48k[i : i + self.AUDIO_FRAMES_PER_BUFFER * 2] - for i in range( - 0, len(txbuffer_48k), self.AUDIO_FRAMES_PER_BUFFER * 2 - ) - ] - # add modulated chunks to fifo buffer - for c in chunk: - # if data is shorter than the expcected audio frames per buffer we need to append 0 - # to prevent the callback from stucking/crashing - if len(c) < self.AUDIO_FRAMES_PER_BUFFER * 2: - c += bytes(self.AUDIO_FRAMES_PER_BUFFER * 2 - len(c)) - self.dataqueue.put(c) - - -test = Test() -test.create_modulation() -test.run_audio() diff --git a/modem/data cemetery/test/util_callback_rx.py b/modem/data cemetery/test/util_callback_rx.py deleted file mode 100644 index 3c17860e..00000000 --- a/modem/data cemetery/test/util_callback_rx.py +++ /dev/null @@ -1,219 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Wed Dec 23 07:04:24 2020 - -@author: DJ2LS -""" - -import argparse -import ctypes -import sys -import time - -import numpy as np -import pyaudio - -sys.path.insert(0, "..") -from modem import codec2 - -# --------------------------------------------GET PARAMETER INPUTS -parser = argparse.ArgumentParser(description="FreeDATA audio test") -parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int) -parser.add_argument("--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int) -parser.add_argument( - "--mode", dest="FREEDV_MODE", type=str, choices=["datac13", "datac1", "datac3"] -) -parser.add_argument( - "--audiodev", - dest="AUDIO_INPUT_DEVICE", - default=-1, - type=int, - help="audio device number to use, use -2 to automatically select a loopback device", -) -parser.add_argument("--debug", dest="DEBUGGING_MODE", action="store_true") -parser.add_argument( - "--timeout", - dest="TIMEOUT", - default=60, - type=int, - help="Timeout (seconds) before test ends", -) -parser.add_argument( - "--list", - dest="LIST", - action="store_true", - help="list audio devices by number and exit", -) - -args, _ = parser.parse_known_args() - -if args.LIST: - p = pyaudio.PyAudio() - for dev in range(p.get_device_count()): - print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) - sys.exit() - - -class Test: - def __init__(self): - self.N_BURSTS = args.N_BURSTS - self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST - self.AUDIO_INPUT_DEVICE = args.AUDIO_INPUT_DEVICE - self.MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value - self.DEBUGGING_MODE = args.DEBUGGING_MODE - self.TIMEOUT = args.TIMEOUT - - # AUDIO PARAMETERS - self.AUDIO_FRAMES_PER_BUFFER = ( - 2400 * 2 - ) # <- consider increasing if you get nread_exceptions > 0 - self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 - self.AUDIO_SAMPLE_RATE_RX = 48000 - - # make sure our resampler will work - assert ( - self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE - ) == codec2.api.FDMDV_OS_48 - - # check if we want to use an audio device then do a pyaudio init - if self.AUDIO_INPUT_DEVICE != -1: - self.p = pyaudio.PyAudio() - # auto search for loopback devices - if self.AUDIO_INPUT_DEVICE == -2: - loopback_list = [ - dev - for dev in range(self.p.get_device_count()) - if "Loopback: PCM" - in self.p.get_device_info_by_index(dev)["name"] - ] - if len(loopback_list) >= 2: - self.AUDIO_INPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX - print(f"loopback_list rx: {loopback_list}", file=sys.stderr) - else: - sys.exit() - - print( - f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \ - AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}", - file=sys.stderr, - ) - - self.stream_rx = self.p.open( - format=pyaudio.paInt16, - channels=1, - rate=self.AUDIO_SAMPLE_RATE_RX, - frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER, - input=True, - output=False, - input_device_index=self.AUDIO_INPUT_DEVICE, - stream_callback=self.callback, - ) - - # open codec2 instance - self.freedv = ctypes.cast(codec2.api.freedv_open(self.MODE), ctypes.c_void_p) - - # get number of bytes per frame for mode - self.bytes_per_frame = int( - codec2.api.freedv_get_bits_per_modem_frame(self.freedv) / 8 - ) - - self.bytes_out = ctypes.create_string_buffer(self.bytes_per_frame) - - codec2.api.freedv_set_frames_per_burst(self.freedv, self.N_FRAMES_PER_BURST) - - self.total_n_bytes = 0 - self.rx_total_frames = 0 - self.rx_frames = 0 - self.rx_bursts = 0 - self.rx_errors = 0 - self.nread_exceptions = 0 - self.timeout = time.time() + self.TIMEOUT - self.receive = True - self.audio_buffer = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER * 2) - self.resampler = codec2.resampler() - - # Copy received 48 kHz to a file. Listen to this file with: - # aplay -r 48000 -f S16_LE rx48_callback.raw - # Corruption of this file is a good way to detect audio card issues - self.frx = open("rx48_callback.raw", mode="wb") - - def callback(self, data_in48k, frame_count, time_info, status): - - x = np.frombuffer(data_in48k, dtype=np.int16) - x.tofile(self.frx) - x = self.resampler.resample48_to_8(x) - self.audio_buffer.push(x) - - # when we have enough samples call FreeDV Rx - nin = codec2.api.freedv_nin(self.freedv) - while self.audio_buffer.nbuffer >= nin: - - # demodulate audio - nbytes = codec2.api.freedv_rawdatarx( - self.freedv, self.bytes_out, self.audio_buffer.buffer.ctypes - ) - self.audio_buffer.pop(nin) - - # call me on every loop! - nin = codec2.api.freedv_nin(self.freedv) - - rx_status = codec2.api.freedv_get_rx_status(self.freedv) - if rx_status & codec2.api.FREEDV_RX_BIT_ERRORS: - self.rx_errors = self.rx_errors + 1 - if self.DEBUGGING_MODE: - rx_status = codec2.api.rx_sync_flags_to_text[rx_status] - print( - "nin: %5d rx_status: %4s naudio_buffer: %4d" - % (nin, rx_status, self.audio_buffer.nbuffer), - file=sys.stderr, - ) - - if nbytes: - self.total_n_bytes = self.total_n_bytes + nbytes - - if nbytes == self.bytes_per_frame: - self.rx_total_frames = self.rx_total_frames + 1 - self.rx_frames = self.rx_frames + 1 - - if self.rx_frames == self.N_FRAMES_PER_BURST: - self.rx_frames = 0 - self.rx_bursts = self.rx_bursts + 1 - - if self.rx_bursts == self.N_BURSTS: - self.receive = False - - return (None, pyaudio.paContinue) - - def run_audio(self): - try: - print("starting pyaudio callback", file=sys.stderr) - self.stream_rx.start_stream() - except Exception as e: - print(f"pyAudio error: {e}", file=sys.stderr) - - while self.receive and time.time() < self.timeout: - time.sleep(1) - - if time.time() >= self.timeout: - print("TIMEOUT REACHED") - - if self.nread_exceptions: - print( - "nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." - % self.nread_exceptions, - file=sys.stderr, - ) - print( - f"RECEIVED BURSTS: {self.rx_bursts} RECEIVED FRAMES: {self.rx_total_frames} RX_ERRORS: {self.rx_errors}", - file=sys.stderr, - ) - self.frx.close() - - # cloese pyaudio instance - self.stream_rx.close() - self.p.terminate() - - -test = Test() -test.run_audio() diff --git a/modem/data cemetery/test/util_callback_rx_outside.py b/modem/data cemetery/test/util_callback_rx_outside.py deleted file mode 100644 index c038c547..00000000 --- a/modem/data cemetery/test/util_callback_rx_outside.py +++ /dev/null @@ -1,217 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Wed Dec 23 07:04:24 2020 - -@author: DJ2LS -""" - -import argparse -import ctypes -import sys -import time - -import numpy as np -import pyaudio - -sys.path.insert(0, "..") -from modem import codec2 - -# --------------------------------------------GET PARAMETER INPUTS -parser = argparse.ArgumentParser(description="FreeDATA audio test") -parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int) -parser.add_argument("--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int) -parser.add_argument( - "--mode", dest="FREEDV_MODE", type=str, choices=["datac13", "datac1", "datac3"] -) -parser.add_argument( - "--audiodev", - dest="AUDIO_INPUT_DEVICE", - default=-1, - type=int, - help="audio device number to use, use -2 to automatically select a loopback device", -) -parser.add_argument("--debug", dest="DEBUGGING_MODE", action="store_true") -parser.add_argument( - "--timeout", - dest="TIMEOUT", - default=10, - type=int, - help="Timeout (seconds) before test ends", -) -parser.add_argument( - "--list", - dest="LIST", - action="store_true", - help="list audio devices by number and exit", -) - -args, _ = parser.parse_known_args() - -if args.LIST: - p = pyaudio.PyAudio() - for dev in range(p.get_device_count()): - print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) - sys.exit() - - -class Test: - def __init__(self): - self.N_BURSTS = args.N_BURSTS - self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST - self.AUDIO_INPUT_DEVICE = args.AUDIO_INPUT_DEVICE - self.MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value - self.DEBUGGING_MODE = args.DEBUGGING_MODE - self.TIMEOUT = args.TIMEOUT - - # AUDIO PARAMETERS - self.AUDIO_FRAMES_PER_BUFFER = ( - 2400 * 2 - ) # <- consider increasing if you get nread_exceptions > 0 - self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 - self.AUDIO_SAMPLE_RATE_RX = 48000 - - # make sure our resampler will work - assert ( - self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE - ) == codec2.api.FDMDV_OS_48 - - # check if we want to use an audio device then do a pyaudio init - if self.AUDIO_INPUT_DEVICE != -1: - self.p = pyaudio.PyAudio() - # auto search for loopback devices - if self.AUDIO_INPUT_DEVICE == -2: - loopback_list = [ - dev - for dev in range(self.p.get_device_count()) - if "Loopback: PCM" - in self.p.get_device_info_by_index(dev)["name"] - ] - if len(loopback_list) >= 2: - self.AUDIO_INPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX - print(f"loopback_list rx: {loopback_list}", file=sys.stderr) - else: - sys.exit() - - print( - f"AUDIO INPUT DEVICE: {self.AUDIO_INPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_INPUT_DEVICE)['name']} \ - AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_RX}", - file=sys.stderr, - ) - - self.stream_rx = self.p.open( - format=pyaudio.paInt16, - channels=1, - rate=self.AUDIO_SAMPLE_RATE_RX, - frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER, - input=True, - output=False, - input_device_index=self.AUDIO_INPUT_DEVICE, - stream_callback=self.callback, - ) - - # open codec2 instance - self.freedv = ctypes.cast(codec2.api.freedv_open(self.MODE), ctypes.c_void_p) - - # get number of bytes per frame for mode - self.bytes_per_frame = int( - codec2.api.freedv_get_bits_per_modem_frame(self.freedv) / 8 - ) - - self.bytes_out = ctypes.create_string_buffer(self.bytes_per_frame * 2) - - codec2.api.freedv_set_frames_per_burst(self.freedv, self.N_FRAMES_PER_BURST) - - self.total_n_bytes = 0 - self.rx_total_frames = 0 - self.rx_frames = 0 - self.rx_bursts = 0 - self.rx_errors = 0 - self.nread_exceptions = 0 - self.timeout = time.time() + self.TIMEOUT - self.receive = True - self.audio_buffer = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER * 2) - self.resampler = codec2.resampler() - - # Copy received 48 kHz to a file. Listen to this file with: - # aplay -r 48000 -f S16_LE rx48_callback.raw - # Corruption of this file is a good way to detect audio card issues - self.frx = open("rx48_callback.raw", mode="wb") - - def callback(self, data_in48k, frame_count, time_info, status): - - x = np.frombuffer(data_in48k, dtype=np.int16) - x.tofile(self.frx) - x = self.resampler.resample48_to_8(x) - self.audio_buffer.push(x) - - return (None, pyaudio.paContinue) - - def run_audio(self): - try: - print("starting pyaudio callback", file=sys.stderr) - self.stream_rx.start_stream() - except Exception as e: - print(f"pyAudio error: {e}", file=sys.stderr) - - while self.receive and time.time() < self.timeout: - # time.sleep(1) - # when we have enough samples call FreeDV Rx - nin = codec2.api.freedv_nin(self.freedv) - while self.audio_buffer.nbuffer >= nin: - - # demodulate audio - nbytes = codec2.api.freedv_rawdatarx( - self.freedv, self.bytes_out, self.audio_buffer.buffer.ctypes - ) - self.audio_buffer.pop(nin) - - # call me on every loop! - nin = codec2.api.freedv_nin(self.freedv) - - rx_status = codec2.api.freedv_get_rx_status(self.freedv) - if rx_status & codec2.api.FREEDV_RX_BIT_ERRORS: - self.rx_errors = self.rx_errors + 1 - if self.DEBUGGING_MODE: - rx_status = codec2.api.rx_sync_flags_to_text[rx_status] - print( - "nin: %5d rx_status: %4s naudio_buffer: %4d" - % (nin, rx_status, self.audio_buffer.nbuffer), - file=sys.stderr, - ) - - if nbytes: - self.total_n_bytes = self.total_n_bytes + nbytes - - if nbytes == self.bytes_per_frame: - self.rx_total_frames = self.rx_total_frames + 1 - self.rx_frames = self.rx_frames + 1 - - if self.rx_frames == self.N_FRAMES_PER_BURST: - self.rx_frames = 0 - self.rx_bursts = self.rx_bursts + 1 - - if self.rx_bursts == self.N_BURSTS: - self.receive = False - if time.time() >= self.timeout: - print("TIMEOUT REACHED") - - if self.nread_exceptions: - print( - "nread_exceptions %d - receive audio lost! Consider increasing Pyaudio frames_per_buffer..." - % self.nread_exceptions, - file=sys.stderr, - ) - print( - f"RECEIVED BURSTS: {self.rx_bursts} RECEIVED FRAMES: {self.rx_total_frames} RX_ERRORS: {self.rx_errors}", - file=sys.stderr, - ) - self.frx.close() - - # cloese pyaudio instance - self.stream_rx.close() - self.p.terminate() - - -test = Test() -test.run_audio() diff --git a/modem/data cemetery/test/util_callback_tx.py b/modem/data cemetery/test/util_callback_tx.py deleted file mode 100644 index ba3ace1d..00000000 --- a/modem/data cemetery/test/util_callback_tx.py +++ /dev/null @@ -1,272 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Wed Dec 23 07:04:24 2020 - -@author: DJ2LS -""" - - -import argparse -import ctypes -import queue -import sys -import time - -import numpy as np -import pyaudio - -sys.path.insert(0, "..") -from modem import codec2 - -# --------------------------------------------GET PARAMETER INPUTS -parser = argparse.ArgumentParser(description="FreeDATA audio test") -parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int) -parser.add_argument("--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int) -parser.add_argument("--delay", dest="DELAY_BETWEEN_BURSTS", default=500, type=int) -parser.add_argument( - "--mode", dest="FREEDV_MODE", type=str, choices=["datac13", "datac1", "datac3"] -) -parser.add_argument( - "--audiodev", - dest="AUDIO_OUTPUT_DEVICE", - default=-1, - type=int, - help="audio device number to use, use -2 to automatically select a loopback device", -) -parser.add_argument( - "--list", - dest="LIST", - action="store_true", - help="list audio devices by number and exit", -) -parser.add_argument( - "--testframes", - dest="TESTFRAMES", - action="store_true", - default=False, - help="generate testframes", -) - -args, _ = parser.parse_known_args() - -if args.LIST: - p = pyaudio.PyAudio() - for dev in range(p.get_device_count()): - print("audiodev: ", dev, p.get_device_info_by_index(dev)["name"]) - sys.exit() - - -class Test: - def __init__(self): - - self.dataqueue = queue.Queue() - self.N_BURSTS = args.N_BURSTS - self.N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST - self.AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE - self.MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value - self.DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS / 1000 - - # AUDIO PARAMETERS - # v-- consider increasing if you get nread_exceptions > 0 - self.AUDIO_FRAMES_PER_BUFFER = 2400 - self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 - self.AUDIO_SAMPLE_RATE_TX = 48000 - - # make sure our resampler will work - assert ( - self.AUDIO_SAMPLE_RATE_TX / self.MODEM_SAMPLE_RATE - ) == codec2.api.FDMDV_OS_48 - - self.transmit = True - - self.resampler = codec2.resampler() - - # check if we want to use an audio device then do a pyaudio init - if self.AUDIO_OUTPUT_DEVICE != -1: - self.p = pyaudio.PyAudio() - # auto search for loopback devices - if self.AUDIO_OUTPUT_DEVICE == -2: - loopback_list = [ - dev - for dev in range(self.p.get_device_count()) - if "Loopback: PCM" - in self.p.get_device_info_by_index(dev)["name"] - ] - if len(loopback_list) >= 2: - self.AUDIO_OUTPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX - print(f"loopback_list rx: {loopback_list}", file=sys.stderr) - else: - sys.exit() - - print( - f"AUDIO OUTPUT DEVICE: {self.AUDIO_OUTPUT_DEVICE} DEVICE: {self.p.get_device_info_by_index(self.AUDIO_OUTPUT_DEVICE)['name']} \ - AUDIO SAMPLE RATE: {self.AUDIO_SAMPLE_RATE_TX}", - file=sys.stderr, - ) - - self.stream_tx = self.p.open( - format=pyaudio.paInt16, - channels=1, - rate=self.AUDIO_SAMPLE_RATE_TX, - frames_per_buffer=self.AUDIO_FRAMES_PER_BUFFER, - input=False, - output=True, - output_device_index=self.AUDIO_OUTPUT_DEVICE, - stream_callback=self.callback, - ) - - # open codec2 instance - self.freedv = ctypes.cast(codec2.api.freedv_open(self.MODE), ctypes.c_void_p) - - # get number of bytes per frame for mode - self.bytes_per_frame = int( - codec2.api.freedv_get_bits_per_modem_frame(self.freedv) / 8 - ) - - self.bytes_out = ctypes.create_string_buffer(self.bytes_per_frame) - - codec2.api.freedv_set_frames_per_burst(self.freedv, self.N_FRAMES_PER_BURST) - - # Copy received 48 kHz to a file. Listen to this file with: - # aplay -r 48000 -f S16_LE rx48_callback.raw - # Corruption of this file is a good way to detect audio card issues - self.ftx = open("tx48_callback.raw", mode="wb") - - # data binary string - if args.TESTFRAMES: - self.data_out = bytearray(14) - self.data_out[:1] = bytes([255]) - self.data_out[1:2] = bytes([1]) - self.data_out[2:] = b"HELLO WORLD" - - else: - self.data_out = b"HELLO WORLD!" - - def callback(self, data_in48k, frame_count, time_info, status): - - data_out48k = self.dataqueue.get() - return (data_out48k, pyaudio.paContinue) - - def run_audio(self): - try: - print("starting pyaudio callback", file=sys.stderr) - self.stream_tx.start_stream() - except Exception as e: - print(f"pyAudio error: {e}", file=sys.stderr) - - sheeps = 0 - while self.transmit: - time.sleep(1) - sheeps = sheeps + 1 - print(f"counting sheeps...{sheeps}") - - self.ftx.close() - - # close pyaudio instance - self.stream_tx.close() - self.p.terminate() - - def create_modulation(self): - - # open codec2 instance - freedv = ctypes.cast(codec2.api.freedv_open(self.MODE), ctypes.c_void_p) - - # 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 - n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv) - mod_out = ctypes.create_string_buffer(n_tx_modem_samples * 2) - - # init buffer for preample - n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples( - freedv - ) - mod_out_preamble = ctypes.create_string_buffer(n_tx_preamble_modem_samples * 2) - - # init buffer for postamble - n_tx_postamble_modem_samples = ( - codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv) - ) - mod_out_postamble = ctypes.create_string_buffer(n_tx_postamble_modem_samples * 2) - - # create buffer for data - buffer = bytearray( - payload_bytes_per_frame - ) # use this if CRC16 checksum is required ( DATA1-3) - buffer[ - : len(self.data_out) - ] = self.data_out # set buffer size to length of data which will be sent - - # 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 - - print( - f"TOTAL BURSTS: {self.N_BURSTS} TOTAL FRAMES_PER_BURST: {self.N_FRAMES_PER_BURST}", - file=sys.stderr, - ) - - for i in range(1, self.N_BURSTS + 1): - - # write preamble to txbuffer - codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble) - txbuffer = bytes(mod_out_preamble) - - # create modulaton for N = FRAMESPERBURST and append it to txbuffer - for n in range(1, self.N_FRAMES_PER_BURST + 1): - - 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) - - print( - f" GENERATING TX BURST: {i}/{self.N_BURSTS} FRAME: {n}/{self.N_FRAMES_PER_BURST}", - file=sys.stderr, - ) - - # append postamble to txbuffer - codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble) - txbuffer += bytes(mod_out_postamble) - - # append a delay between bursts as audio silence - samples_delay = int(self.MODEM_SAMPLE_RATE * self.DELAY_BETWEEN_BURSTS) - mod_out_silence = ctypes.create_string_buffer(samples_delay * 2) - txbuffer += bytes(mod_out_silence) - print( - f"samples_delay: {samples_delay} DELAY_BETWEEN_BURSTS: {self.DELAY_BETWEEN_BURSTS}", - file=sys.stderr, - ) - - # resample up to 48k (resampler works on np.int16) - x = np.frombuffer(txbuffer, dtype=np.int16) - txbuffer_48k = self.resampler.resample8_to_48(x) - - # split modualted audio to chunks - # https://newbedev.com/how-to-split-a-byte-string-into-separate-bytes-in-python - txbuffer_48k = bytes(txbuffer_48k) - chunk = [ - txbuffer_48k[i : i + self.AUDIO_FRAMES_PER_BUFFER * 2] - for i in range( len(txbuffer_48k), self.AUDIO_FRAMES_PER_BUFFER * 2) - ] - # add modulated chunks to fifo buffer - for c in chunk: - # if data is shorter than the expcected audio frames per buffer we need to append 0 - # to prevent the callback from stucking/crashing - if len(c) < self.AUDIO_FRAMES_PER_BUFFER * 2: - c += bytes(self.AUDIO_FRAMES_PER_BUFFER * 2 - len(c)) - self.dataqueue.put(c) - - -test = Test() -test.create_modulation() -test.run_audio() diff --git a/modem/data cemetery/test/util_chat_text_1.py b/modem/data cemetery/test/util_chat_text_1.py deleted file mode 100644 index f9e9eae8..00000000 --- a/modem/data cemetery/test/util_chat_text_1.py +++ /dev/null @@ -1,219 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Send-side station emulator for connect frame tests over a high quality simulated audio channel. - -Near end-to-end test for sending / receiving connection control frames through the -Modem and modem and back through on the other station. Data injection initiates from the -queue used by the daemon process into and out of the Modem. - -Invoked from test_chat_text.py. - -@author: N2KIQ -""" - -import base64 -import json -import time -from pprint import pformat -from typing import Callable - -import codec2 -import data_handler -import helpers -import modem -import sock -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem -import structlog - - -def t_setup( - mycall: str, - dxcall: str, - lowbwmode: bool, - t_transmit, - t_process_data, - tmp_path, -): - # Disable data_handler testmode - This is required to test a conversation. - data_handler.TESTMODE = False - modem.RXCHANNEL = tmp_path / "hfchannel1" - modem.TESTMODE = True - modem.TXCHANNEL = tmp_path / "hfchannel2" - HamlibParam.hamlib_radiocontrol = "disabled" - Modem.low_bandwidth_mode = lowbwmode - Station.mygrid = bytes("AA12aa", "utf-8") - Modem.respond_to_cq = True - Station.ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - # override ARQ SESSION STATE for allowing disconnect command - ARQ.arq_session_state = "connected" - - mycallsign = helpers.callsign_to_bytes(mycall) - mycallsign = helpers.bytes_to_callsign(mycallsign) - Station.mycallsign = mycallsign - Station.mycallsign_crc = helpers.get_crc_24(Station.mycallsign) - - dxcallsign = helpers.callsign_to_bytes(dxcall) - dxcallsign = helpers.bytes_to_callsign(dxcallsign) - Station.dxcallsign = dxcallsign - Station.dxcallsign_crc = helpers.get_crc_24(Station.dxcallsign) - - # Create the Modem - modem = data_handler.DATA() - orig_rx_func = data_handler.DATA.process_data - data_handler.DATA.process_data = t_process_data - modem.log = structlog.get_logger("station1_DATA") - # Limit the frame-ack timeout - modem.time_list_low_bw = [3, 1, 1] - modem.time_list_high_bw = [3, 1, 1] - modem.time_list = [3, 1, 1] - # Limit number of retries - modem.rx_n_max_retries_per_burst = 5 - - # Create the modem - t_modem = modem.RF() - orig_tx_func = modem.RF.transmit - modem.RF.transmit = t_transmit - t_modem.log = structlog.get_logger("station1_RF") - - return modem, orig_rx_func, orig_tx_func - - -def t_highsnr_arq_short_station1( - parent_pipe, - freedv_mode: str, - n_frames_per_burst: int, - mycall: str, - dxcall: str, - message: str, - lowbwmode: bool, - tmp_path, -): - log = structlog.get_logger("station1") - orig_tx_func: Callable - orig_rx_func: Callable - log.info("t_highsnr_arq_short_station1:", TMP_PATH=tmp_path) - - def t_transmit(self, mode, repeats: int, repeat_delay: int, frames: bytearray): - """'Wrap' RF.transmit function to extract the arguments.""" - nonlocal orig_tx_func, parent_pipe - - t_frames = frames - parent_pipe.send(t_frames) - # log.info("S1 TX: ", frames=t_frames) - for item in t_frames: - frametype = int.from_bytes(item[:1], "big") # type: ignore - log.info("S1 TX: ", mode=static.FRAME_TYPE(frametype).name) - - if ( - Modem.low_bandwidth_mode - and frametype == static.FRAME_TYPE.ARQ_DC_OPEN_W.value - ): - mesg = ( - "enqueue_frame_for_tx: Low BW global " - "but DC Open narrow frame NOT chosen." - ) - # Low bandwidth data type, wide bandwidth frame type - log.error(mesg) - assert False, mesg - if ( - not Modem.low_bandwidth_mode - and frametype == static.FRAME_TYPE.ARQ_DC_OPEN_N.value - ): - mesg = ( - "enqueue_frame_for_tx: High BW global " - "but DC Open wide frame NOT chosen." - ) - # High bandwidth data type, low bandwidth frame type - log.error(mesg) - assert False, mesg - - # Apologies for the Python "magic." "orig_func" is a pointer to the - # original function captured before this one was put in place. - orig_tx_func(self, mode, repeats, repeat_delay, frames) # type: ignore - - def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): - """'Wrap' DATA.process_data function to extract the arguments.""" - nonlocal orig_rx_func, parent_pipe - - t_bytes_out = bytes(bytes_out) - parent_pipe.send(t_bytes_out) - log.debug( - "S1 RX: ", - bytes_out=t_bytes_out, - bytes_per_frame=bytes_per_frame, - ) - frametype = int.from_bytes(t_bytes_out[:1], "big") - log.info("S1 RX: ", RX=frametype) - - # Apologies for the Python "magic." "orig_func" is a pointer to the - # original function captured before this one was put in place. - orig_rx_func(self, bytes_out, freedv, bytes_per_frame) # type: ignore - - modem, orig_rx_func, orig_tx_func = t_setup( - mycall, dxcall, lowbwmode, t_transmit, t_process_data, tmp_path - ) - - log.info("t_highsnr_arq_short_station1:", RXCHANNEL=modem.RXCHANNEL) - log.info("t_highsnr_arq_short_station1:", TXCHANNEL=modem.TXCHANNEL) - - # Construct message to dxstation. - b64_str = str(base64.b64encode(bytes(message, "UTF-8")), "UTF-8").strip() - data = { - "type": "arq", - "command": "send_raw", - "parameter": [ - { - "data": b64_str, - "dxcallsign": dxcall, - "mode": codec2.FREEDV_MODE[freedv_mode].value, - "n_frames": n_frames_per_burst, - } - ], - } - - sock.process_modem_commands(json.dumps(data, indent=None)) - - # Assure the test completes. - timeout = time.time() + 25 - # Compare with the string conversion instead of repeatedly dumping - # the queue to an object for comparisons. - while '"arq":"transmission","status":"transmitted"' not in str( - sock.SOCKET_QUEUE.queue - ): - if time.time() > timeout: - log.warning("station1 TIMEOUT", first=True) - break - time.sleep(0.1) - log.info("station1, first", arq_state=pformat(ARQ.arq_state)) - - data = {"type": "arq", "command": "disconnect", "dxcallsign": dxcall} - sock.process_modem_commands(json.dumps(data, indent=None)) - time.sleep(0.5) - # override ARQ SESSION STATE for allowing disconnect command - ARQ.arq_session_state = "connected" - sock.process_modem_commands(json.dumps(data, indent=None)) - - # Allow enough time for this side to process the disconnect frame. - timeout = time.time() + 20 - while ARQ.arq_state or modem.data_queue_transmit.queue: - if time.time() > timeout: - log.error("station1", TIMEOUT=True) - break - time.sleep(0.5) - log.info("station1", arq_state=pformat(ARQ.arq_state)) - - # log.info("S1 DQT: ", DQ_Tx=pformat(modem.data_queue_transmit.queue)) - # log.info("S1 DQR: ", DQ_Rx=pformat(modem.data_queue_received.queue)) - log.info("S1 Socket: ", socket_queue=pformat(sock.SOCKET_QUEUE.queue)) - - assert '"arq":"transmission","status":"transmitting"' in str( - sock.SOCKET_QUEUE.queue - ) - assert '"arq":"transmission","status":"transmitted"' in str(sock.SOCKET_QUEUE.queue) - assert '"arq":"transmission","status":"failed"' not in str(sock.SOCKET_QUEUE.queue) - assert '"percent":100' in str(sock.SOCKET_QUEUE.queue) - - assert '"command_response":"disconnect","status":"OK"' in str( - sock.SOCKET_QUEUE.queue - ) - log.error("station1: Exiting!") diff --git a/modem/data cemetery/test/util_chat_text_2.py b/modem/data cemetery/test/util_chat_text_2.py deleted file mode 100644 index a9b8c5bf..00000000 --- a/modem/data cemetery/test/util_chat_text_2.py +++ /dev/null @@ -1,163 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Receive-side station emulator for connect frame tests over a high quality simulated audio channel. - -Near end-to-end test for sending / receiving connection control frames through the -Modem and modem and back through on the other station. Data injection initiates from the -queue used by the daemon process into and out of the Modem. - -Invoked from test_chat_text.py. - -@author: N2KIQ -""" - -import time -from pprint import pformat -from typing import Callable - -import data_handler -import helpers -import modem -import sock -from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem -import structlog - - -def t_setup( - mycall: str, - dxcall: str, - lowbwmode: bool, - t_transmit, - t_process_data, - tmp_path, -): - # Disable data_handler testmode - This is required to test a conversation. - data_handler.TESTMODE = False - modem.RXCHANNEL = tmp_path / "hfchannel2" - modem.TESTMODE = True - modem.TXCHANNEL = tmp_path / "hfchannel1" - HamlibParam.hamlib_radiocontrol = "disabled" - Modem.low_bandwidth_mode = lowbwmode - Station.mygrid = bytes("AA12aa", "utf-8") - Modem.respond_to_cq = True - Station.ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - # override ARQ SESSION STATE for allowing disconnect command - ARQ.arq_session_state = "connected" - - mycallsign = helpers.callsign_to_bytes(mycall) - mycallsign = helpers.bytes_to_callsign(mycallsign) - Station.mycallsign = mycallsign - Station.mycallsign_crc = helpers.get_crc_24(Station.mycallsign) - - dxcallsign = helpers.callsign_to_bytes(dxcall) - dxcallsign = helpers.bytes_to_callsign(dxcallsign) - Station.dxcallsign = dxcallsign - Station.dxcallsign_crc = helpers.get_crc_24(Station.dxcallsign) - - # Create the Modem - modem = data_handler.DATA() - orig_rx_func = data_handler.DATA.process_data - data_handler.DATA.process_data = t_process_data - modem.log = structlog.get_logger("station2_DATA") - # Limit the frame-ack timeout - modem.time_list_low_bw = [1, 1, 1] - modem.time_list_high_bw = [1, 1, 1] - modem.time_list = [1, 1, 1] - # Limit number of retries - modem.rx_n_max_retries_per_burst = 5 - - # Create the modem - t_modem = modem.RF() - orig_tx_func = modem.RF.transmit - modem.RF.transmit = t_transmit - t_modem.log = structlog.get_logger("station2_RF") - - return modem, orig_rx_func, orig_tx_func - - -def t_highsnr_arq_short_station2( - parent_pipe, - freedv_mode: str, - n_frames_per_burst: int, - mycall: str, - dxcall: str, - message: str, - lowbwmode: bool, - tmp_path, -): - log = structlog.get_logger("station2") - orig_tx_func: Callable - orig_rx_func: Callable - log.info("t_highsnr_arq_short_station2:", TMP_PATH=tmp_path) - - def t_transmit(self, mode, repeats: int, repeat_delay: int, frames: bytearray): - """'Wrap' RF.transmit function to extract the arguments.""" - nonlocal orig_tx_func, parent_pipe - - t_frames = frames - parent_pipe.send(t_frames) - # log.info("S2 TX: ", frames=t_frames) - for item in t_frames: - frametype = int.from_bytes(item[:1], "big") # type: ignore - log.info("S2 TX: ", TX=frametype) - - # Apologies for the Python "magic." "orig_func" is a pointer to the - # original function captured before this one was put in place. - orig_tx_func(self, mode, repeats, repeat_delay, frames) # type: ignore - - def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): - """'Wrap' DATA.process_data function to extract the arguments.""" - nonlocal orig_rx_func, parent_pipe - - t_bytes_out = bytes(bytes_out) - parent_pipe.send(t_bytes_out) - log.debug( - "S2 RX: ", - bytes_out=t_bytes_out, - bytes_per_frame=bytes_per_frame, - ) - frametype = int.from_bytes(t_bytes_out[:1], "big") - log.info("S2 RX: ", RX=frametype) - - # Apologies for the Python "magic." "orig_func" is a pointer to the - # original function captured before this one was put in place. - orig_rx_func(self, bytes_out, freedv, bytes_per_frame) # type: ignore - - modem, orig_rx_func, orig_tx_func = t_setup( - mycall, dxcall, lowbwmode, t_transmit, t_process_data, tmp_path - ) - - log.info("t_highsnr_arq_short_station2:", RXCHANNEL=modem.RXCHANNEL) - log.info("t_highsnr_arq_short_station2:", TXCHANNEL=modem.TXCHANNEL) - - # Assure the test completes. - timeout = time.time() + 25 - # Compare with the string conversion instead of repeatedly dumping - # the queue to an object for comparisons. - while ( - '"arq":"transmission","status":"received"' not in str(sock.SOCKET_QUEUE.queue) - or ARQ.arq_state - ): - if time.time() > timeout: - log.warning("station2 TIMEOUT", first=True) - break - time.sleep(0.5) - log.info("station2, first", arq_state=pformat(ARQ.arq_state)) - - # Allow enough time for this side to receive the disconnect frame. - timeout = time.time() + 20 - while '"arq":"session","status":"close"' not in str(sock.SOCKET_QUEUE.queue): - if time.time() > timeout: - log.warning("station2", TIMEOUT=True) - break - time.sleep(0.5) - log.info("station2", arq_state=pformat(ARQ.arq_state)) - - # log.info("S2 DQT: ", DQ_Tx=pformat(modem.data_queue_transmit.queue)) - # log.info("S2 DQR: ", DQ_Rx=pformat(modem.data_queue_received.queue)) - log.info("S2 Socket: ", socket_queue=pformat(sock.SOCKET_QUEUE.queue)) - - assert '"arq":"transmission","status":"received"' in str(sock.SOCKET_QUEUE.queue) - - assert '"arq":"session","status":"close"' in str(sock.SOCKET_QUEUE.queue) - log.warning("station2: Exiting!") diff --git a/modem/data cemetery/test/util_datac13.py b/modem/data cemetery/test/util_datac13.py deleted file mode 100644 index 6dcc0b65..00000000 --- a/modem/data cemetery/test/util_datac13.py +++ /dev/null @@ -1,309 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Send- and receive-side station emulator for control frame tests over a high quality -simulated audio channel. - -Near end-to-end test for sending / receiving control frames through the Modem and modem -and back through on the other station. Data injection initiates from the queue used -by the daemon process into and out of the ModemParam. - -Invoked from test_datac13.py. - -@author: N2KIQ -""" - -import json -import time -from pprint import pformat -from typing import Callable, Tuple - -import data_handler -import helpers -import modem -import sock -from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem -from static import FRAME_TYPE as FR_TYPE -import structlog -#from static import FRAME_TYPE as FR_TYPE - - -def t_setup( - station: int, - mycall: str, - dxcall: str, - rx_channel: str, - tx_channel: str, - lowbwmode: bool, - t_transmit, - t_process_data, - tmp_path, -): - # Disable data_handler testmode - This is required to test a conversation. - data_handler.TESTMODE = True - - # Enable socket testmode for overriding socket class - sock.TESTMODE = True - - modem.RXCHANNEL = tmp_path / rx_channel - modem.TESTMODE = True - modem.TXCHANNEL = tmp_path / tx_channel - HamlibParam.hamlib_radiocontrol = "disabled" - ModemParam.low_bandwidth_mode = lowbwmode or True - Station.mygrid = bytes("AA12aa", "utf-8") - ModemParam.respond_to_cq = True - Station.ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - - mycallsign = helpers.callsign_to_bytes(mycall) - mycallsign = helpers.bytes_to_callsign(mycallsign) - Station.mycallsign = mycallsign - Station.mycallsign_crc = helpers.get_crc_24(Station.mycallsign) - - dxcallsign = helpers.callsign_to_bytes(dxcall) - dxcallsign = helpers.bytes_to_callsign(dxcallsign) - Station.dxcallsign = dxcallsign - Station.dxcallsign_crc = helpers.get_crc_24(Station.dxcallsign) - - # Create the Modem - modem_data_handler = data_handler.DATA() - orig_rx_func = data_handler.DATA.process_data - data_handler.DATA.process_data = t_process_data - modem_data_handler.log = structlog.get_logger(f"station{station}_DATA") - # Limit the frame-ack timeout - modem_data_handler.time_list_low_bw = [8, 8, 8] - modem_data_handler.time_list_high_bw = [8, 8, 8] - modem_data_handler.time_list = [8, 8, 8] - # Limit number of retries - modem_data_handler.rx_n_max_retries_per_burst = 4 - ModemParam.tx_delay = 50 # add additional delay time for passing test - - - # Create the modem - t_modem = modem.RF() - orig_tx_func = modem.RF.transmit - modem.RF.transmit = t_transmit - t_modem.log = structlog.get_logger(f"station{station}_RF") - - return modem_data_handler, orig_rx_func, orig_tx_func - - -def t_datac13_1( - parent_pipe, - mycall: str, - dxcall: str, - config: Tuple, - tmp_path, -): - log = structlog.get_logger("station1") - orig_tx_func: Callable - orig_rx_func: Callable - log.debug("t_datac13_1:", TMP_PATH=tmp_path) - - # Unpack tuple - data, timeout_duration, tx_check, _, final_tx_check, _ = config - - def t_transmit(self, mode, repeats: int, repeat_delay: int, frames: bytearray): - """'Wrap' RF.transmit function to extract the arguments.""" - nonlocal orig_tx_func, parent_pipe - - t_frames = frames - parent_pipe.send(t_frames) - # log.info("S1 TX: ", frames=t_frames) - for item in t_frames: - frametype = int.from_bytes(item[:1], "big") # type: ignore - log.info("S1 TX: ", TX=FR_TYPE(frametype).name) - - # Apologies for the Python "magic." "orig_func" is a pointer to the - # original function captured before this one was put in place. - orig_tx_func(self, mode, repeats, repeat_delay, frames) # type: ignore - - def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): - """'Wrap' DATA.process_data function to extract the arguments.""" - nonlocal orig_rx_func, parent_pipe - - t_bytes_out = bytes(bytes_out) - parent_pipe.send(t_bytes_out) - log.debug( - "S1 RX: ", - bytes_out=t_bytes_out, - bytes_per_frame=bytes_per_frame, - ) - frametype = int.from_bytes(t_bytes_out[:1], "big") - log.info("S1 RX: ", RX=FR_TYPE(frametype).name) - - # Apologies for the Python "magic." "orig_func" is a pointer to the - # original function captured before this one was put in place. - orig_rx_func(self, bytes_out, freedv, bytes_per_frame) # type: ignore - - modem_data_handler, orig_rx_func, orig_tx_func = t_setup( - 1, - mycall, - dxcall, - "hfchannel1", - "hfchannel2", - True, - t_transmit, - t_process_data, - tmp_path, - ) - - log.info("t_datac13_1:", RXCHANNEL=modem.RXCHANNEL) - log.info("t_datac13_1:", TXCHANNEL=modem.TXCHANNEL) - - time.sleep(0.5) - if "stop" in data["command"]: - log.debug("t_datac13_1: STOP test, setting Modem state") - ModemParam.modem_state = "BUSY" - ARQ.arq_state = True - sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) - time.sleep(0.5) - sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) - - # Assure the test completes. - timeout = time.time() + timeout_duration + 5 - while tx_check not in str(sock.SOCKET_QUEUE.queue): - if time.time() > timeout: - log.warning( - "station1 TIMEOUT", - first=True, - queue=str(sock.SOCKET_QUEUE.queue), - tx_check=tx_check, - ) - break - time.sleep(0.5) - log.info("station1, first") - # override ARQ SESSION STATE for allowing disconnect command - ARQ.arq_session_state = "connected" - data = {"type": "arq", "command": "disconnect", "dxcallsign": dxcall} - sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) - time.sleep(0.5) - - # Allow enough time for this side to process the disconnect frame. - timeout = time.time() + timeout_duration - while modem_data_handler.data_queue_transmit.queue: - if time.time() > timeout: - log.warning("station1", TIMEOUT=True, dq_tx=modem_data_handler.data_queue_transmit.queue) - break - time.sleep(0.5) - log.info("station1, final") - - # log.info("S1 DQT: ", DQ_Tx=pformat(ModemParam.data_queue_transmit.queue)) - # log.info("S1 DQR: ", DQ_Rx=pformat(ModemParam.data_queue_received.queue)) - log.debug("S1 Socket: ", socket_queue=pformat(sock.SOCKET_QUEUE.queue)) - - for item in final_tx_check: - assert item in str( - sock.SOCKET_QUEUE.queue - ), f"{item} not found in {str(sock.SOCKET_QUEUE.queue)}" - assert ':"failed"' not in str(sock.SOCKET_QUEUE.queue) - - assert '"command_response":"disconnect","status":"OK"' in str( - sock.SOCKET_QUEUE.queue - ) - log.warning("station1: Exiting!") - - -def t_datac13_2( - parent_pipe, - mycall: str, - dxcall: str, - config: Tuple, - tmp_path, -): - log = structlog.get_logger("station2") - orig_tx_func: Callable - orig_rx_func: Callable - log.debug("t_datac13_2:", TMP_PATH=tmp_path) - - # Unpack tuple - data, timeout_duration, _, rx_check, _, final_rx_check = config - - def t_transmit(self, mode, repeats: int, repeat_delay: int, frames: bytearray): - """'Wrap' RF.transmit function to extract the arguments.""" - nonlocal orig_tx_func, parent_pipe - - t_frames = frames - parent_pipe.send(t_frames) - # log.info("S2 TX: ", frames=t_frames) - for item in t_frames: - frametype = int.from_bytes(item[:1], "big") # type: ignore - log.info("S2 TX: ", TX=FR_TYPE(frametype).name) - - # Apologies for the Python "magic." "orig_func" is a pointer to the - # original function captured before this one was put in place. - orig_tx_func(self, mode, repeats, repeat_delay, frames) # type: ignore - - def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): - """'Wrap' DATA.process_data function to extract the arguments.""" - nonlocal orig_rx_func, parent_pipe - - t_bytes_out = bytes(bytes_out) - parent_pipe.send(t_bytes_out) - log.debug( - "S2 RX: ", - bytes_out=t_bytes_out, - bytes_per_frame=bytes_per_frame, - ) - frametype = int.from_bytes(t_bytes_out[:1], "big") - log.info("S2 RX: ", RX=FR_TYPE(frametype).name) - - # Apologies for the Python "magic." "orig_func" is a pointer to the - # original function captured before this one was put in place. - orig_rx_func(self, bytes_out, freedv, bytes_per_frame) # type: ignore - - _, orig_rx_func, orig_tx_func = t_setup( - 2, - mycall, - dxcall, - "hfchannel2", - "hfchannel1", - True, - t_transmit, - t_process_data, - tmp_path, - ) - - log.info("t_datac13_2:", RXCHANNEL=modem.RXCHANNEL) - log.info("t_datac13_2:", TXCHANNEL=modem.TXCHANNEL) - - # TODO Why do we need this when calling CQ? - #if "cq" in data: - # t_data = {"type": "arq", "command": "stop_transmission"} - # sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(t_data, indent=None)) - # sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(t_data, indent=None)) - - # Assure the test completes. - timeout = time.time() + timeout_duration - # Compare with the string conversion instead of repeatedly dumping - # the queue to an object for comparisons. - while rx_check not in str(sock.SOCKET_QUEUE.queue): - if time.time() > timeout: - log.warning( - "station2 TIMEOUT", - first=True, - queue=str(sock.SOCKET_QUEUE.queue), - rx_check=rx_check, - ) - break - time.sleep(0.5) - log.info("station2, first") - - # Allow enough time for this side to receive the disconnect frame. - timeout = time.time() + timeout_duration - while '"arq":"session","status":"close"' not in str(sock.SOCKET_QUEUE.queue): - if time.time() > timeout: - log.warning("station2", TIMEOUT=True, queue=str(sock.SOCKET_QUEUE.queue)) - break - time.sleep(0.5) - log.info("station2, final") - - # log.info("S2 DQT: ", DQ_Tx=pformat(ModemParam.data_queue_transmit.queue)) - # log.info("S2 DQR: ", DQ_Rx=pformat(ModemParam.data_queue_received.queue)) - log.debug("S2 Socket: ", socket_queue=pformat(sock.SOCKET_QUEUE.queue)) - - for item in final_rx_check: - assert item in str( - sock.SOCKET_QUEUE.queue - ), f"{item} not found in {str(sock.SOCKET_QUEUE.queue)}" - # TODO Not sure why we need this for every test run - # assert '"arq":"session","status":"close"' in str(sock.SOCKET_QUEUE.queue) - log.warning("station2: Exiting!") diff --git a/modem/data cemetery/test/util_datac13_negative.py b/modem/data cemetery/test/util_datac13_negative.py deleted file mode 100644 index f09e948e..00000000 --- a/modem/data cemetery/test/util_datac13_negative.py +++ /dev/null @@ -1,311 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Negative test utilities for datac13 frames. - -@author: kronenpj -""" - -import json -import time -from pprint import pformat -from typing import Callable, Tuple - -import data_handler -import helpers -import modem -import sock -from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem -import structlog -#from static import FRAME_TYPE as FR_TYPE - - -def t_setup( - station: int, - mycall: str, - dxcall: str, - rx_channel: str, - tx_channel: str, - lowbwmode: bool, - t_transmit, - t_process_data, - tmp_path, -): - # Disable data_handler testmode - This is required to test a conversation. - data_handler.TESTMODE = False - - # Enable socket testmode for overriding socket class - sock.TESTMODE = True - - modem.RXCHANNEL = tmp_path / rx_channel - modem.TESTMODE = True - modem.TXCHANNEL = tmp_path / tx_channel - HamlibParam.hamlib_radiocontrol = "disabled" - Modem.low_bandwidth_mode = lowbwmode or True - Station.mygrid = bytes("AA12aa", "utf-8") - Modem.respond_to_cq = True - Station.ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - - mycallsign_bytes = helpers.callsign_to_bytes(mycall) - mycallsign = helpers.bytes_to_callsign(mycallsign_bytes) - Station.mycallsign = mycallsign - Station.mycallsign_crc = helpers.get_crc_24(mycallsign) - - dxcallsign_bytes = helpers.callsign_to_bytes(dxcall) - dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes) - Station.dxcallsign = dxcallsign - Station.dxcallsign_crc = helpers.get_crc_24(dxcallsign) - - # Create the Modem - modem_data_handler = data_handler.DATA() - orig_rx_func = data_handler.DATA.process_data - data_handler.DATA.process_data = t_process_data - modem_data_handler.log = structlog.get_logger(f"station{station}_DATA") - # Limit the frame-ack timeout - modem_data_handler.time_list_low_bw = [8, 8, 8] - modem_data_handler.time_list_high_bw = [8, 8, 8] - modem_data_handler.time_list = [8, 8, 8] - # Limit number of retries - modem_data_handler.rx_n_max_retries_per_burst = 4 - ModemParam.tx_delay = 50 # add additional delay time for passing test - # Create the modem - t_modem = modem.RF() - orig_tx_func = modem.RF.transmit - modem.RF.transmit = t_transmit - t_modem.log = structlog.get_logger(f"station{station}_RF") - - return modem_data_handler, orig_rx_func, orig_tx_func - - -def t_datac13_1( - parent_pipe, - mycall: str, - dxcall: str, - config: Tuple, - tmp_path, -): - log = structlog.get_logger("station1") - orig_tx_func: Callable - orig_rx_func: Callable - log.debug("t_datac13_1:", TMP_PATH=tmp_path) - - # Unpack tuple - data, timeout_duration, tx_check, _, final_tx_check, _ = config - - def t_transmit(self, mode, repeats: int, repeat_delay: int, frames: bytearray): - """'Wrap' RF.transmit function to extract the arguments.""" - nonlocal orig_tx_func, parent_pipe - - t_frames = frames - parent_pipe.send(t_frames) - # log.info("S1 TX: ", frames=t_frames) - for item in t_frames: - frametype = int.from_bytes(item[:1], "big") # type: ignore - log.info("S1 TX: ", TX=FR_TYPE(frametype).name) - - # Apologies for the Python "magic." "orig_func" is a pointer to the - # original function captured before this one was put in place. - orig_tx_func(self, mode, repeats, repeat_delay, frames) # type: ignore - - def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): - """'Wrap' DATA.process_data function to extract the arguments.""" - nonlocal orig_rx_func, parent_pipe - - t_bytes_out = bytes(bytes_out) - parent_pipe.send(t_bytes_out) - log.debug( - "S1 RX: ", - bytes_out=t_bytes_out, - bytes_per_frame=bytes_per_frame, - ) - frametype = int.from_bytes(t_bytes_out[:1], "big") - log.info("S1 RX: ", RX=FR_TYPE(frametype).name) - - # Apologies for the Python "magic." "orig_func" is a pointer to the - # original function captured before this one was put in place. - orig_rx_func(self, bytes_out, freedv, bytes_per_frame) # type: ignore - - modem_data_handler, orig_rx_func, orig_tx_func = t_setup( - 1, - mycall, - dxcall, - "hfchannel1", - "hfchannel2", - True, - t_transmit, - t_process_data, - tmp_path, - ) - - log.info("t_datac13_1:", RXCHANNEL=modem.RXCHANNEL) - log.info("t_datac13_1:", TXCHANNEL=modem.TXCHANNEL) - - orig_dxcall = Station.dxcallsign - if "stop" in data["command"]: - time.sleep(0.5) - log.debug( - "t_datac13_1: STOP test, setting Modem state", - mycall=Station.mycallsign, - dxcall=Station.dxcallsign, - ) - Station.dxcallsign = helpers.callsign_to_bytes(data["dxcallsign"]) - Station.dxcallsign_CRC = helpers.get_crc_24(Station.dxcallsign) - Modem.modem_state = "BUSY" - ARQ.arq_state = True - sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) - sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) - - # Assure the test completes. - timeout = time.time() + timeout_duration - while tx_check not in str(sock.SOCKET_QUEUE.queue): - if time.time() > timeout: - log.warning( - "station1 TIMEOUT", - first=True, - queue=str(sock.SOCKET_QUEUE.queue), - tx_check=tx_check, - ) - break - time.sleep(0.1) - log.info("station1, first") - - if "stop" in data["command"]: - time.sleep(0.5) - log.debug("STOP test, resetting DX callsign") - Station.dxcallsign = orig_dxcall - Station.dxcallsign_CRC = helpers.get_crc_24(Station.dxcallsign) - # override ARQ SESSION STATE for allowing disconnect command - ARQ.arq_session_state = "connected" - data = {"type": "arq", "command": "disconnect", "dxcallsign": dxcall} - sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) - time.sleep(0.5) - - # Allow enough time for this side to process the disconnect frame. - timeout = time.time() + timeout_duration - while modem_data_handler.data_queue_transmit.queue: - if time.time() > timeout: - log.warning("station1", TIMEOUT=True, dq_tx=modem_data_handler.data_queue_transmit.queue) - break - time.sleep(0.5) - log.info("station1, final") - - # log.info("S1 DQT: ", DQ_Tx=pformat(modem.data_queue_transmit.queue)) - # log.info("S1 DQR: ", DQ_Rx=pformat(modem.data_queue_received.queue)) - log.debug("S1 Socket: ", socket_queue=pformat(sock.SOCKET_QUEUE.queue)) - - for item in final_tx_check: - assert item in str( - sock.SOCKET_QUEUE.queue - ), f"{item} not found in {str(sock.SOCKET_QUEUE.queue)}" - - assert '"command_response":"disconnect","status":"OK"' in str( - sock.SOCKET_QUEUE.queue - ) - log.warning("station1: Exiting!") - - -def t_datac13_2( - parent_pipe, - mycall: str, - dxcall: str, - config: Tuple, - tmp_path, -): - log = structlog.get_logger("station2") - orig_tx_func: Callable - orig_rx_func: Callable - log.debug("t_datac13_2:", TMP_PATH=tmp_path) - - # Unpack tuple - data, timeout_duration, _, rx_check, _, final_rx_check = config - - def t_transmit(self, mode, repeats: int, repeat_delay: int, frames: bytearray): - """'Wrap' RF.transmit function to extract the arguments.""" - nonlocal orig_tx_func, parent_pipe - - t_frames = frames - parent_pipe.send(t_frames) - # log.info("S2 TX: ", frames=t_frames) - for item in t_frames: - frametype = int.from_bytes(item[:1], "big") # type: ignore - log.info("S2 TX: ", TX=FR_TYPE(frametype).name) - - # Apologies for the Python "magic." "orig_func" is a pointer to the - # original function captured before this one was put in place. - orig_tx_func(self, mode, repeats, repeat_delay, frames) # type: ignore - - def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): - """'Wrap' DATA.process_data function to extract the arguments.""" - nonlocal orig_rx_func, parent_pipe - - t_bytes_out = bytes(bytes_out) - parent_pipe.send(t_bytes_out) - log.debug( - "S2 RX: ", - bytes_out=t_bytes_out, - bytes_per_frame=bytes_per_frame, - ) - frametype = int.from_bytes(t_bytes_out[:1], "big") - log.info("S2 RX: ", RX=FR_TYPE(frametype).name) - - # Apologies for the Python "magic." "orig_func" is a pointer to the - # original function captured before this one was put in place. - orig_rx_func(self, bytes_out, freedv, bytes_per_frame) # type: ignore - - _, orig_rx_func, orig_tx_func = t_setup( - 2, - mycall, - dxcall, - "hfchannel2", - "hfchannel1", - True, - t_transmit, - t_process_data, - tmp_path, - ) - - log.info("t_datac13_2:", RXCHANNEL=modem.RXCHANNEL) - log.info("t_datac13_2:", TXCHANNEL=modem.TXCHANNEL) - log.info("t_datac13_2:", mycall=Station.mycallsign) - - if "cq" in data: - t_data = {"type": "arq", "command": "stop_transmission"} - sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(t_data, indent=None)) - sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(t_data, indent=None)) - - # Assure the test completes. - timeout = time.time() + timeout_duration - # Compare with the string conversion instead of repeatedly dumping - # the queue to an object for comparisons. - while rx_check not in str(sock.SOCKET_QUEUE.queue): - if time.time() > timeout: - log.warning( - "station2 TIMEOUT", - first=True, - queue=str(sock.SOCKET_QUEUE.queue), - rx_check=rx_check, - ) - break - time.sleep(0.5) - log.info("station2, first") - - # Allow enough time for this side to receive the disconnect frame. - timeout = time.time() + timeout_duration - while '"arq":"session", "status":"close"' not in str(sock.SOCKET_QUEUE.queue): - if time.time() > timeout: - log.warning("station2", TIMEOUT=True, queue=str(sock.SOCKET_QUEUE.queue)) - break - time.sleep(0.5) - log.info("station2, final") - - # log.info("S2 DQT: ", DQ_Tx=pformat(modem.data_queue_transmit.queue)) - # log.info("S2 DQR: ", DQ_Rx=pformat(modem.data_queue_received.queue)) - log.debug("S2 Socket: ", socket_queue=pformat(sock.SOCKET_QUEUE.queue)) - - for item in final_rx_check: - assert item not in str( - sock.SOCKET_QUEUE.queue - ), f"{item} found in {str(sock.SOCKET_QUEUE.queue)}" - # TODO Not sure why we need this for every test run - # assert '"arq":"session","status":"close"' in str(sock.SOCKET_QUEUE.queue) - log.warning("station2: Exiting!") diff --git a/modem/data cemetery/test/util_modem_IRS.py b/modem/data cemetery/test/util_modem_IRS.py deleted file mode 100644 index cb865ebf..00000000 --- a/modem/data cemetery/test/util_modem_IRS.py +++ /dev/null @@ -1,108 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Receive-side station emulator for connect frame tests over a high quality simulated audio channel. - -Near end-to-end test for sending / receiving connection control frames through the -TNC and modem and back through on the other station. Data injection initiates from the -queue used by the daemon process into and out of the TNC. - -Invoked from test_modem.py. - -@author: N2KIQ -""" - -import signal -import sys -import time -from typing import Callable - -import structlog - -sys.path.insert(0, "../modem") -import data_handler -import helpers -import modem -import sock -from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem - -IRS_original_arq_cleanup: Callable -MESSAGE: str - -log = structlog.get_logger("util_modem_IRS") - - -def irs_arq_cleanup(): - """Replacement for modem.arq_cleanup to detect when to exit process.""" - log.info( - "irs_arq_cleanup", socket_queue=sock.SOCKET_QUEUE.queue, message=MESSAGE.lower() - ) - if '"arq":"transmission","status":"stopped"' in str(sock.SOCKET_QUEUE.queue): - # log.info("irs_arq_cleanup", socket_queue=sock.SOCKET_QUEUE.queue) - time.sleep(2) - if f'"{MESSAGE.lower()}":"receiving"' not in str( - sock.SOCKET_QUEUE.queue - ) and f'"{MESSAGE.lower()}":"received"' not in str(sock.SOCKET_QUEUE.queue): - print(f"{MESSAGE} was not received.") - log.info("irs_arq_cleanup", socket_queue=sock.SOCKET_QUEUE.queue) - # sys.exit does not terminate threads, and os_exit doesn't allow coverage collection. - signal.raise_signal(signal.SIGKILL) - - signal.raise_signal(signal.SIGTERM) - IRS_original_arq_cleanup() - - -def t_arq_irs(*args): - # not sure why importing at top level isn't working - import modem - import data_handler - # pylint: disable=global-statement - global IRS_original_arq_cleanup, MESSAGE - - MESSAGE = args[0] - tmp_path = args[1] - - sock.log = structlog.get_logger("util_modem_IRS_sock") - - # enable testmode - data_handler.TESTMODE = True - modem.RXCHANNEL = tmp_path / "hfchannel2" - modem.TESTMODE = True - modem.TXCHANNEL = tmp_path / "hfchannel1" - HamlibParam.hamlib_radiocontrol = "disabled" - Modem.respond_to_cq = True - log.info("t_arq_irs:", RXCHANNEL=modem.RXCHANNEL) - log.info("t_arq_irs:", TXCHANNEL=modem.TXCHANNEL) - - mycallsign = bytes("DN2LS-2", "utf-8") - mycallsign = helpers.callsign_to_bytes(mycallsign) - Station.mycallsign = helpers.bytes_to_callsign(mycallsign) - Station.mycallsign_CRC = helpers.get_crc_24(Station.mycallsign) - Station.mygrid = bytes("AA12aa", "utf-8") - Station.ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - - # start data handler - data_handler = data_handler.DATA() - data_handler.log = structlog.get_logger("util_modem_IRS_DATA") - - # Inject a way to exit the TNC infinite loop - IRS_original_arq_cleanup = data_handler.arq_cleanup - data_handler.arq_cleanup = irs_arq_cleanup - - # start modem - t_modem = modem.RF() - t_modem.log = structlog.get_logger("util_modem_IRS_RF") - - # Set timeout - timeout = time.time() + 15 - - while time.time() < timeout: - time.sleep(0.1) - - log.warning("queue:", queue=sock.SOCKET_QUEUE.queue) - - assert not "TIMEOUT!" - - -if __name__ == "__main__": - print("This cannot be run as an application.") - sys.exit(1) diff --git a/modem/data cemetery/test/util_modem_ISS.py b/modem/data cemetery/test/util_modem_ISS.py deleted file mode 100644 index 1d06203f..00000000 --- a/modem/data cemetery/test/util_modem_ISS.py +++ /dev/null @@ -1,156 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Send-side station emulator for connect frame tests over a high quality simulated audio channel. - -Near end-to-end test for sending / receiving connection control frames through the -TNC and modem and back through on the other station. Data injection initiates from the -queue used by the daemon process into and out of the TNC. - -Invoked from test_modem.py. - -@author: DJ2LS, N2KIQ -""" - -import json -import signal -import sys -import time -from typing import Callable - -import structlog - -sys.path.insert(0, "../modem") -import data_handler -import helpers -import modem -import sock -import static -from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem - -ISS_original_arq_cleanup: Callable -MESSAGE: str - -log = structlog.get_logger("util_modem_ISS") - - -def iss_arq_cleanup(): - """Replacement for modem.arq_cleanup to detect when to exit process.""" - log.info( - "iss_arq_cleanup", socket_queue=sock.SOCKET_QUEUE.queue, message=MESSAGE.lower() - ) - if '"arq":"transmission","status":"stopped"' in str(sock.SOCKET_QUEUE.queue): - # log.info("iss_arq_cleanup", socket_queue=sock.SOCKET_QUEUE.queue) - time.sleep(1) - if f'"{MESSAGE.lower()}":"transmitting"' not in str( - sock.SOCKET_QUEUE.queue - ) and f'"{MESSAGE.lower()}":"sending"' not in str(sock.SOCKET_QUEUE.queue): - print(f"{MESSAGE} was not sent.") - log.info("iss_arq_cleanup", socket_queue=sock.SOCKET_QUEUE.queue) - # sys.exit does not terminate threads, and os_exit doesn't allow coverage collection. - signal.raise_signal(signal.SIGKILL) - - signal.raise_signal(signal.SIGTERM) - ISS_original_arq_cleanup() - - -def t_arq_iss(*args): - # not sure why importing at top level isn't working - import modem - import data_handler - # pylint: disable=global-statement - global ISS_original_arq_cleanup, MESSAGE - - MESSAGE = args[0] - tmp_path = args[1] - - sock.log = structlog.get_logger("util_modem_ISS_sock") - - # enable testmode - data_handler.TESTMODE = True - modem.RXCHANNEL = tmp_path / "hfchannel1" - modem.TESTMODE = True - modem.TXCHANNEL = tmp_path / "hfchannel2" - HamlibParam.hamlib_radiocontrol = "disabled" - log.info("t_arq_iss:", RXCHANNEL=modem.RXCHANNEL) - log.info("t_arq_iss:", TXCHANNEL=modem.TXCHANNEL) - - mycallsign = bytes("DJ2LS-2", "utf-8") - mycallsign = helpers.callsign_to_bytes(mycallsign) - Station.mycallsign = helpers.bytes_to_callsign(mycallsign) - Station.mycallsign_CRC = helpers.get_crc_24(Station.mycallsign) - Station.mygrid = bytes("AA12aa", "utf-8") - Station.ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - - dxcallsign = b"DN2LS-0" - dxcallsign = helpers.callsign_to_bytes(dxcallsign) - dxcallsign = helpers.bytes_to_callsign(dxcallsign) - Station.dxcallsign = dxcallsign - Station.dxcallsign_CRC = helpers.get_crc_24(Station.dxcallsign) - - bytes_out = b'{"dt":"f","fn":"zeit.txt","ft":"text\\/plain","d":"data:text\\/plain;base64,MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=MyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5CgMyBtb2Rlcywgb2huZSBjbGFzcwowLjAwMDk2OTQ4MTE4MDk5MTg0MTcKCjIgbW9kZXMsIG9obmUgY2xhc3MKMC4wMDA5NjY1NDUxODkxMjI1Mzk0CgoxIG1vZGUsIG9obmUgY2xhc3MKMC4wMDA5NjY5NzY1NTU4Nzc4MjA5Cg=","crc":"123123123"}' - - # start data handler - data_handler = data_handler.DATA() - data_handler.log = structlog.get_logger("util_modem_ISS_DATA") - - # Inject a way to exit the TNC infinite loop - ISS_original_arq_cleanup = data_handler.arq_cleanup - data_handler.arq_cleanup = iss_arq_cleanup - - # start modem - t_modem = modem.RF() - t_modem.log = structlog.get_logger("util_modem_ISS_RF") - - # mode = codec2.freedv_get_mode_value_by_name(FREEDV_MODE) - # n_frames_per_burst = N_FRAMES_PER_BURST - - # add command to data qeue - """ - elif data[0] == 'ARQ_RAW': - # [0] ARQ_RAW - # [1] DATA_OUT bytes - # [2] MODE int - # [3] N_FRAMES_PER_BURST int - # [4] self.transmission_uuid str - # [5] mycallsign with ssid - """ - # data_handler.DATA_QUEUE_TRANSMIT.put(['ARQ_RAW', bytes_out, 255, n_frames_per_burst, '123', b'DJ2LS-0']) - - data = {} - if MESSAGE in ["CONNECT"]: - data = { - "type": "arq", - "command": "connect", - "dxcallsign": str(dxcallsign, encoding="UTF-8"), - } - else: - assert not MESSAGE, f"{MESSAGE} not known to test." - - time.sleep(2.5) - - sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) - sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) - sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) - - time.sleep(7.5) - - data = {"type": "arq", "command": "stop_transmission"} - sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) - - time.sleep(2.5) - sock.ThreadedTCPRequestHandler.process_modem_commands(None,json.dumps(data, indent=None)) - - # Set timeout - timeout = time.time() + 15 - - while time.time() < timeout: - time.sleep(0.1) - - log.warning("queue:", queue=sock.SOCKET_QUEUE.queue) - - assert not "TIMEOUT!" - - -if __name__ == "__main__": - print("This cannot be run as an application.") - sys.exit(-1) diff --git a/modem/data cemetery/test/util_multimode_rx.py b/modem/data cemetery/test/util_multimode_rx.py deleted file mode 100755 index aef517fc..00000000 --- a/modem/data cemetery/test/util_multimode_rx.py +++ /dev/null @@ -1,254 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Receive-side station emulator for test frame tests over a high quality audio channel -using a physical sound card or STDIO. - -Legacy test for sending / receiving connection test frames through the codec2 and -back through on the other station. Data injection initiates directly through -the codec2 API. Tests all three codec2 data frames simultaneously. - -Invoked from CMake, test_highsnr_stdio_P_P_multi.py, and many test_virtual[1-3]*.sh. - -@author: DJ2LS -""" - -import argparse -import ctypes -import sys -import time -from typing import List - -import numpy as np -import pyaudio - -sys.path.insert(0, "..") -from modem import codec2 - - -def test_mm_rx(): - # AUDIO PARAMETERS - AUDIO_FRAMES_PER_BUFFER = 2400 * 2 - MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 - AUDIO_SAMPLE_RATE_RX = 48000 - # make sure our resampler will work - assert (AUDIO_SAMPLE_RATE_RX / MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 - - # SET COUNTERS - rx_bursts_datac = [0, 0, 0] - rx_frames_datac = [0, 0, 0] - rx_total_frames_datac = [0, 0, 0] - - # time meassurement - time_end_datac = [0.0, 0.0, 0.0] - time_needed_datac = [0.0, 0.0, 0.0] - time_start_datac = [0.0, 0.0, 0.0] - - datac_buffer: List[codec2.audio_buffer] = [] - datac_bytes_out: List[ctypes.Array] = [] - datac_bytes_per_frame = [] - datac_freedv: List[ctypes.c_void_p] = [] - - args = parse_arguments() - - if args.LIST: - p_audio = pyaudio.PyAudio() - for dev in range(p_audio.get_device_count()): - print("audiodev: ", dev, p_audio.get_device_info_by_index(dev)["name"]) - sys.exit() - - N_BURSTS = args.N_BURSTS - N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST - AUDIO_INPUT_DEVICE = args.AUDIO_INPUT_DEVICE - DEBUGGING_MODE = args.DEBUGGING_MODE - MAX_TIME = args.TIMEOUT - - # open codec2 instances - for idx in range(3): - datac_freedv.append( - ctypes.cast( - codec2.api.freedv_open(codec2.FREEDV_MODE.datac13.value), ctypes.c_void_p - ) - ) - datac_bytes_per_frame.append( - int(codec2.api.freedv_get_bits_per_modem_frame(datac_freedv[idx]) / 8) - ) - datac_bytes_out.append(ctypes.create_string_buffer(datac_bytes_per_frame[idx])) - codec2.api.freedv_set_frames_per_burst(datac_freedv[idx], N_FRAMES_PER_BURST) - datac_buffer.append(codec2.audio_buffer(2 * AUDIO_FRAMES_PER_BUFFER)) - - resampler = codec2.resampler() - - # check if we want to use an audio device then do a pyaudio init - if AUDIO_INPUT_DEVICE != -1: - p_audio = pyaudio.PyAudio() - # auto search for loopback devices - if AUDIO_INPUT_DEVICE == -2: - loopback_list = [ - dev - for dev in range(p_audio.get_device_count()) - if "Loopback: PCM" in p_audio.get_device_info_by_index(dev)["name"] - ] - - if len(loopback_list) >= 2: - AUDIO_INPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX - print(f"loopback_list rx: {loopback_list}", file=sys.stderr) - else: - sys.exit() - - print( - f"AUDIO INPUT DEVICE: {AUDIO_INPUT_DEVICE} " - f"DEVICE: {p_audio.get_device_info_by_index(AUDIO_INPUT_DEVICE)['name']} " - f"AUDIO SAMPLE RATE: {AUDIO_SAMPLE_RATE_RX}", - file=sys.stderr, - ) - stream_rx = p_audio.open( - format=pyaudio.paInt16, - channels=1, - rate=AUDIO_SAMPLE_RATE_RX, - frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, - input=True, - input_device_index=AUDIO_INPUT_DEVICE, - ) - - timeout = time.time() + MAX_TIME - print(time.time(), MAX_TIME, timeout) - receive = True - nread_exceptions = 0 - - # initial nin values - datac_nin = [0, 0, 0] - for idx in range(3): - datac_nin[idx] = codec2.api.freedv_nin(datac_freedv[idx]) - - def print_stats(time_datac13, time_datac1, time_datac3): - if not DEBUGGING_MODE: - return - - time_datac = [time_datac13, time_datac1, time_datac3] - datac_rxstatus = ["", "", ""] - for idx in range(3): - datac_rxstatus[idx] = codec2.api.rx_sync_flags_to_text[ - codec2.api.freedv_get_rx_status(datac_freedv[idx]) - ] - - text_out = "" - for idx in range(3): - text_out += f"NIN{idx}: {datac_nin[idx]:5d} " - text_out += f"RX_STATUS{idx}: {datac_rxstatus[idx]:4s} " - text_out += f"TIME: {round(time_datac[idx], 4):.4f} | " - text_out = text_out.rstrip(" ").rstrip("|").rstrip(" ") - print(text_out, file=sys.stderr) - - while receive and time.time() < timeout: - if AUDIO_INPUT_DEVICE != -1: - try: - data_in48k = stream_rx.read( - AUDIO_FRAMES_PER_BUFFER, exception_on_overflow=True - ) - except OSError as err: - print(err, file=sys.stderr) - if "Input overflowed" in str(err): - nread_exceptions += 1 - if "Stream closed" in str(err): - print("Ending....") - receive = False - else: - data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER * 2) - - # insert samples in buffer - audio_buffer = np.frombuffer(data_in48k, dtype=np.int16) - if len(audio_buffer) != AUDIO_FRAMES_PER_BUFFER: - print("len(x)", len(audio_buffer)) - receive = False - audio_buffer = resampler.resample48_to_8(audio_buffer) - - for idx in range(3): - datac_buffer[idx].push(audio_buffer) - while datac_buffer[idx].nbuffer >= datac_nin[idx]: - # demodulate audio - time_start_datac[idx] = time.time() - nbytes = codec2.api.freedv_rawdatarx( - datac_freedv[idx], - datac_bytes_out[idx], - datac_buffer[idx].buffer.ctypes, - ) - time_end_datac[idx] = time.time() - datac_buffer[idx].pop(datac_nin[idx]) - datac_nin[idx] = codec2.api.freedv_nin(datac_freedv[idx]) - if nbytes == datac_bytes_per_frame[idx]: - rx_total_frames_datac[idx] += 1 - rx_frames_datac[idx] += 1 - - if rx_frames_datac[idx] == N_FRAMES_PER_BURST: - rx_frames_datac[idx] = 0 - rx_bursts_datac[idx] += 1 - time_needed_datac[idx] = time_end_datac[idx] - time_start_datac[idx] - print_stats( - time_needed_datac[0], time_needed_datac[1], time_needed_datac[2] - ) - - if ( - rx_bursts_datac[0] == N_BURSTS - and rx_bursts_datac[1] == N_BURSTS - and rx_bursts_datac[2] == N_BURSTS - ): - receive = False - - if nread_exceptions: - print( - f"nread_exceptions {nread_exceptions:d} - receive audio lost! " - "Consider increasing Pyaudio frames_per_buffer...", - file=sys.stderr, - ) - # INFO IF WE REACHED TIMEOUT - if time.time() > timeout: - print("TIMEOUT REACHED", file=sys.stderr) - - print( - f"DATAC13: {rx_bursts_datac[0]}/{rx_total_frames_datac[0]} " - f"DATAC1: {rx_bursts_datac[1]}/{rx_total_frames_datac[1]} " - f"DATAC3: {rx_bursts_datac[2]}/{rx_total_frames_datac[2]}", - file=sys.stderr, - ) - - if AUDIO_INPUT_DEVICE != -1: - stream_rx.close() - p_audio.terminate() - - -def parse_arguments(): - # --------------------------------------------GET PARAMETER INPUTS - parser = argparse.ArgumentParser(description="Simons TEST TNC") - parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int) - parser.add_argument( - "--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int - ) - parser.add_argument( - "--audiodev", - dest="AUDIO_INPUT_DEVICE", - default=-1, - type=int, - help="audio device number to use", - ) - parser.add_argument("--debug", dest="DEBUGGING_MODE", action="store_true") - parser.add_argument( - "--list", - dest="LIST", - action="store_true", - help="list audio devices by number and exit", - ) - parser.add_argument( - "--timeout", - dest="TIMEOUT", - default=60, - type=int, - help="Timeout (seconds) before test ends", - ) - - args, _ = parser.parse_known_args() - return args - - -if __name__ == "__main__": - test_mm_rx() diff --git a/modem/data cemetery/test/util_multimode_tx.py b/modem/data cemetery/test/util_multimode_tx.py deleted file mode 100644 index f9f5338b..00000000 --- a/modem/data cemetery/test/util_multimode_tx.py +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Send-side station emulator for test frame tests over a high quality audio channel -using a physical sound card or STDIO. - -Legacy test for sending / receiving connection test frames through the codec2 and -back through on the other station. Data injection initiates directly through -the codec2 API. Tests all three codec2 data frames simultaneously. - -Invoked from CMake, test_highsnr_stdio_P_P_multi.py, and many test_virtual[1-3]*.sh. - -@author: DJ2LS -""" - -import argparse -import ctypes -import sys -import time - -import numpy as np -import pyaudio - -sys.path.insert(0, "..") -from modem import codec2 - - -def test_mm_tx(): - MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 - AUDIO_SAMPLE_RATE_TX = 48000 - assert (AUDIO_SAMPLE_RATE_TX % MODEM_SAMPLE_RATE) == 0 - - args = parse_arguments() - - if args.LIST: - p_audio = pyaudio.PyAudio() - for dev in range(p_audio.get_device_count()): - print("audiodev: ", dev, p_audio.get_device_info_by_index(dev)["name"]) - sys.exit() - - N_BURSTS = args.N_BURSTS - N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST - DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS / 1000 - AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE - - resampler = codec2.resampler() - - # Data binary string - data_out = b"HELLO WORLD!" - - modes = [ - codec2.FREEDV_MODE.datac13.value, - codec2.FREEDV_MODE.datac1.value, - codec2.FREEDV_MODE.datac3.value, - ] - - if AUDIO_OUTPUT_DEVICE != -1: - p_audio = pyaudio.PyAudio() - # Auto search for loopback devices - if AUDIO_OUTPUT_DEVICE == -2: - loopback_list = [ - dev - for dev in range(p_audio.get_device_count()) - if "Loopback: PCM" in p_audio.get_device_info_by_index(dev)["name"] - ] - - if len(loopback_list) >= 2: - AUDIO_OUTPUT_DEVICE = loopback_list[1] # 0 = RX 1 = TX - print(f"loopback_list tx: {loopback_list}", file=sys.stderr) - else: - sys.exit() - - # AUDIO PARAMETERS - AUDIO_FRAMES_PER_BUFFER = 2400 - # pyaudio init - stream_tx = p_audio.open( - format=pyaudio.paInt16, - channels=1, - rate=AUDIO_SAMPLE_RATE_TX, - frames_per_buffer=AUDIO_FRAMES_PER_BUFFER, # n_nom_modem_samples - output=True, - output_device_index=AUDIO_OUTPUT_DEVICE, - ) - - for mode in modes: - freedv = ctypes.cast(codec2.api.freedv_open(mode), ctypes.c_void_p) - - n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv) - mod_out = ctypes.create_string_buffer(2 * n_tx_modem_samples) - - n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples( - freedv - ) - mod_out_preamble = ctypes.create_string_buffer(2 * n_tx_preamble_modem_samples) - - n_tx_postamble_modem_samples = ( - codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv) - ) - mod_out_postamble = ctypes.create_string_buffer( - 2 * n_tx_postamble_modem_samples - ) - - bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8) - payload_per_frame = bytes_per_frame - 2 - - buffer = bytearray(payload_per_frame) - # Set buffer size to length of data which will be sent - buffer[: len(data_out)] = data_out - - # Generate CRC16 - crc = ctypes.c_ushort( - codec2.api.freedv_gen_crc16(bytes(buffer), payload_per_frame) - ) - # Convert CRC to 2 byte hex string - crc = crc.value.to_bytes(2, byteorder="big") - buffer += crc # Append crc16 to buffer - data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) - - for brst in range(1, N_BURSTS + 1): - # Write preamble to txbuffer - codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble) - txbuffer = bytes(mod_out_preamble) - - # Create modulaton for N = FRAMESPERBURST and append it to txbuffer - for frm in range(1, N_FRAMES_PER_BURST + 1): - data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) - # Modulate DATA and save it into mod_out pointer - codec2.api.freedv_rawdatatx(freedv, mod_out, data) - - txbuffer += bytes(mod_out) - print( - f"TX BURST: {brst}/{N_BURSTS} FRAME: {frm}/{N_FRAMES_PER_BURST}", - file=sys.stderr, - ) - - # Append postamble to txbuffer - codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble) - txbuffer += bytes(mod_out_postamble) - - # Append a delay between bursts as audio silence - samples_delay = int(MODEM_SAMPLE_RATE * DELAY_BETWEEN_BURSTS) - mod_out_silence = ctypes.create_string_buffer(samples_delay * 2) - txbuffer += bytes(mod_out_silence) - - # Resample up to 48k (resampler works on np.int16) - audio_buffer = np.frombuffer(txbuffer, dtype=np.int16) - txbuffer_48k = resampler.resample8_to_48(audio_buffer) - - # Check if we want to use an audio device or stdout - if AUDIO_OUTPUT_DEVICE != -1: - stream_tx.write(txbuffer_48k.tobytes()) - else: - # This test needs a lot of time, so we are having a look at times... - starttime = time.time() - - # Print data to terminal for piping the output to other programs - sys.stdout.buffer.write(txbuffer_48k) - sys.stdout.flush() - - # and at least print the needed time to see which time we needed - timeneeded = time.time() - starttime - # print(f"time: {timeneeded} buffer: {len(txbuffer)}", file=sys.stderr) - - # and at last check if we had an opened pyaudio instance and close it - if AUDIO_OUTPUT_DEVICE != -1: - time.sleep(stream_tx.get_output_latency()) - stream_tx.stop_stream() - stream_tx.close() - p_audio.terminate() - - -def parse_arguments(): - # GET PARAMETER INPUTS - parser = argparse.ArgumentParser(description="FreeDATA TEST") - parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int) - parser.add_argument( - "--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int - ) - parser.add_argument("--delay", dest="DELAY_BETWEEN_BURSTS", default=500, type=int) - parser.add_argument( - "--audiodev", - dest="AUDIO_OUTPUT_DEVICE", - default=-1, - type=int, - help="audio output device number to use", - ) - parser.add_argument( - "--list", - dest="LIST", - action="store_true", - help="list audio devices by number and exit", - ) - - args, _ = parser.parse_known_args() - return args - - -if __name__ == "__main__": - test_mm_tx() diff --git a/modem/data cemetery/test/util_rx.py b/modem/data cemetery/test/util_rx.py deleted file mode 100644 index 127ef4bf..00000000 --- a/modem/data cemetery/test/util_rx.py +++ /dev/null @@ -1,253 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Receive-side station emulator for test frame tests over a high quality audio channel -using a physical sound card or STDIO. - -Legacy test for sending / receiving connection test frames through the codec2 and -back through on the other station. Data injection initiates directly through -the codec2 API. - -Invoked from CMake, test_highsnr_stdio_{P_C, P_P}_datacx.py, and many test_virtual[1-3]*.sh. - -@author: DJ2LS -""" - -import argparse -import ctypes -import sys -import time - -import numpy as np -import sounddevice as sd - -# pylint: disable=wrong-import-position -sys.path.insert(0, "..") -sys.path.insert(0, "../modem") -from modem import codec2 - - -def util_rx(): - args = parse_arguments() - - if args.LIST: - - devices = sd.query_devices(device=None, kind=None) - for index, device in enumerate(devices): - print(f"{index} {device['name']}") - index += 1 - # pylint: disable=protected-access - sd._terminate() - sys.exit() - - N_BURSTS = args.N_BURSTS - N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST - AUDIO_INPUT_DEVICE = args.AUDIO_INPUT_DEVICE - MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value - DEBUGGING_MODE = args.DEBUGGING_MODE - MAX_TIME = args.TIMEOUT - - # AUDIO PARAMETERS - # v-- consider increasing if you get nread_exceptions > 0 - AUDIO_FRAMES_PER_BUFFER = 2400 * 2 - MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 - AUDIO_SAMPLE_RATE_RX = 48000 - - # make sure our resampler will work - assert (AUDIO_SAMPLE_RATE_RX / MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 # type: ignore - - # check if we want to use an audio device then do a pyaudio init - if AUDIO_INPUT_DEVICE != -1: - # auto search for loopback devices - if AUDIO_INPUT_DEVICE == -2: - loopback_list = [] - - devices = sd.query_devices(device=None, kind=None) - - for index, device in enumerate(devices): - if "Loopback: PCM" in device["name"]: - print(index) - loopback_list.append(index) - - if loopback_list: - # 0 = RX 1 = TX - AUDIO_INPUT_DEVICE = loopback_list[0] - print(f"loopback_list tx: {loopback_list}", file=sys.stderr) - else: - print("not enough audio loopback devices ready...") - print("you should wait about 30 seconds...") - - sd._terminate() - sys.exit() - print(f"AUDIO INPUT DEVICE: {AUDIO_INPUT_DEVICE}", file=sys.stderr) - - # audio stream init - stream_rx = sd.RawStream( - channels=1, - dtype="int16", - device=AUDIO_INPUT_DEVICE, - samplerate=AUDIO_SAMPLE_RATE_RX, - blocksize=4800, - ) - stream_rx.start() - - # ---------------------------------------------------------------- - # DATA CHANNEL INITIALISATION - - # open codec2 instance - freedv = ctypes.cast(codec2.api.freedv_open(MODE), ctypes.c_void_p) - - # 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 - - n_max_modem_samples = codec2.api.freedv_get_n_max_modem_samples(freedv) - bytes_out = ctypes.create_string_buffer(bytes_per_frame) - - codec2.api.freedv_set_frames_per_burst(freedv, N_FRAMES_PER_BURST) - - total_n_bytes = 0 - rx_total_frames = 0 - rx_frames = 0 - rx_bursts = 0 - rx_errors = 0 - nread_exceptions = 0 - timeout = time.time() + MAX_TIME - receive = True - audio_buffer = codec2.audio_buffer(AUDIO_FRAMES_PER_BUFFER * 2) - resampler = codec2.resampler() - - # time meassurement - time_start = 0 - time_end = 0 - - with open("rx48.raw", mode="wb") as frx: - # initial number of samples we need - nin = codec2.api.freedv_nin(freedv) - while receive and time.time() < timeout: - if AUDIO_INPUT_DEVICE != -1: - try: - # data_in48k = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = True) - data_in48k, overflowed = stream_rx.read(AUDIO_FRAMES_PER_BUFFER) # type: ignore - except OSError as err: - print(err, file=sys.stderr) - # if str(err).find("Input overflowed") != -1: - # nread_exceptions += 1 - # if str(err).find("Stream closed") != -1: - # print("Ending...") - # receive = False - else: - data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER * 2) - - # insert samples in buffer - x = np.frombuffer(data_in48k, dtype=np.int16) # type: ignore - # print(x) - # x = data_in48k - x.tofile(frx) - if len(x) != AUDIO_FRAMES_PER_BUFFER: - receive = False - x = resampler.resample48_to_8(x) - audio_buffer.push(x) - - # when we have enough samples call FreeDV Rx - while audio_buffer.nbuffer >= nin: - # start time measurement - time_start = time.time() - # demodulate audio - nbytes = codec2.api.freedv_rawdatarx( - freedv, bytes_out, audio_buffer.buffer.ctypes - ) - time_end = time.time() - - audio_buffer.pop(nin) - - # call me on every loop! - nin = codec2.api.freedv_nin(freedv) - - rx_status = codec2.api.freedv_get_rx_status(freedv) - if rx_status & codec2.api.FREEDV_RX_BIT_ERRORS: - rx_errors = rx_errors + 1 - if DEBUGGING_MODE: - rx_status = codec2.api.rx_sync_flags_to_text[rx_status] # type: ignore - time_needed = time_end - time_start - - print( - f"nin: {nin:5d} rx_status: {rx_status:4s} " - f"naudio_buffer: {audio_buffer.nbuffer:4d} time: {time_needed:4f}", - file=sys.stderr, - ) - - if nbytes: - total_n_bytes += nbytes - - if nbytes == bytes_per_frame: - rx_total_frames += 1 - rx_frames += 1 - - if rx_frames == N_FRAMES_PER_BURST: - rx_frames = 0 - rx_bursts += 1 - - if rx_bursts == N_BURSTS: - receive = False - - if time.time() >= timeout: - print("TIMEOUT REACHED") - - time.sleep(0.01) - - if nread_exceptions: - print( - f"nread_exceptions {nread_exceptions:d} - receive audio lost! " - "Consider increasing Pyaudio frames_per_buffer...", - file=sys.stderr, - ) - print( - f"RECEIVED BURSTS: {rx_bursts} " - f"RECEIVED FRAMES: {rx_total_frames} " - f"RX_ERRORS: {rx_errors}", - file=sys.stderr, - ) - # and at last check if we had an opened audio instance and close it - if AUDIO_INPUT_DEVICE != -1: - sd._terminate() - - -def parse_arguments(): - # --------------------------------------------GET PARAMETER INPUTS - parser = argparse.ArgumentParser(description="Simons TEST TNC") - parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int) - parser.add_argument( - "--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int - ) - parser.add_argument( - "--mode", dest="FREEDV_MODE", type=str, choices=["datac13", "datac1", "datac3"] - ) - parser.add_argument( - "--audiodev", - dest="AUDIO_INPUT_DEVICE", - default=-1, - type=int, - help="audio device number to use, use -2 to automatically select a loopback device", - ) - parser.add_argument("--debug", dest="DEBUGGING_MODE", action="store_true") - parser.add_argument( - "--timeout", - dest="TIMEOUT", - default=60, - type=int, - help="Timeout (seconds) before test ends", - ) - parser.add_argument( - "--list", - dest="LIST", - action="store_true", - help="list audio devices by number and exit", - ) - - args, _ = parser.parse_known_args() - return args - - -if __name__ == "__main__": - util_rx() diff --git a/modem/data cemetery/test/util_tx.py b/modem/data cemetery/test/util_tx.py deleted file mode 100644 index 3c0e6d40..00000000 --- a/modem/data cemetery/test/util_tx.py +++ /dev/null @@ -1,229 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Send-side station emulator for test frame tests over a high quality audio channel -using a physical sound card or STDIO. - -Legacy test for sending / receiving connection test frames through the codec2 and -back through on the other station. Data injection initiates directly through -the codec2 API. - -Invoked from CMake, test_highsnr_stdio_{P_C, P_P}_datacx.py, and many test_virtual[1-3]*.sh. - -@author: DJ2LS -""" - -import argparse -import ctypes -import sys - -import numpy as np -import sounddevice as sd - -sys.path.insert(0, "..") -from modem import codec2 - - -def util_tx(): - args = parse_arguments() - - if args.LIST: - devices = sd.query_devices(device=None, kind=None) - - for index, device in enumerate(devices): - print(f"{index} {device['name']}") - sd._terminate() - sys.exit() - - N_BURSTS = args.N_BURSTS - N_FRAMES_PER_BURST = args.N_FRAMES_PER_BURST - DELAY_BETWEEN_BURSTS = args.DELAY_BETWEEN_BURSTS / 1000 - AUDIO_OUTPUT_DEVICE = args.AUDIO_OUTPUT_DEVICE - - MODE = codec2.FREEDV_MODE[args.FREEDV_MODE].value - - # AUDIO PARAMETERS - AUDIO_FRAMES_PER_BUFFER = 2400 - MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 - AUDIO_SAMPLE_RATE_TX = 48000 - assert (AUDIO_SAMPLE_RATE_TX % MODEM_SAMPLE_RATE) == 0 # type: ignore - - # check if we want to use an audio device then do a pyaudio init - if AUDIO_OUTPUT_DEVICE != -1: - # auto search for loopback devices - if AUDIO_OUTPUT_DEVICE == -2: - loopback_list = [] - - devices = sd.query_devices(device=None, kind=None) - - for index, device in enumerate(devices): - if "Loopback: PCM" in device["name"]: - print(index) - loopback_list.append(index) - - if loopback_list: - # 0 = RX 1 = TX - AUDIO_OUTPUT_DEVICE = loopback_list[-1] - print(f"loopback_list tx: {loopback_list}", file=sys.stderr) - else: - print("not enough audio loopback devices ready...") - print("you should wait about 30 seconds...") - sd._terminate() - sys.exit() - print(f"AUDIO OUTPUT DEVICE: {AUDIO_OUTPUT_DEVICE}", file=sys.stderr) - - # audio stream init - stream_tx = sd.RawStream( - channels=1, - dtype="int16", - device=(0, AUDIO_OUTPUT_DEVICE), - samplerate=AUDIO_SAMPLE_RATE_TX, - blocksize=4800, - ) - - resampler = codec2.resampler() - - # data binary string - if args.TESTFRAMES: - data_out = bytearray(14) - data_out[:1] = bytes([255]) - data_out[1:2] = bytes([1]) - data_out[2:] = b"HELLO WORLD" - else: - data_out = b"HELLO WORLD!" - - # ---------------------------------------------------------------- - - # Open codec2 instance - freedv = ctypes.cast(codec2.api.freedv_open(MODE), ctypes.c_void_p) - - # 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 - n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv) - mod_out = ctypes.create_string_buffer(n_tx_modem_samples * 2) - - # Init buffer for preample - n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples( - freedv - ) - mod_out_preamble = ctypes.create_string_buffer(n_tx_preamble_modem_samples * 2) - - # Init buffer for postamble - n_tx_postamble_modem_samples = codec2.api.freedv_get_n_tx_postamble_modem_samples( - freedv - ) - mod_out_postamble = ctypes.create_string_buffer(n_tx_postamble_modem_samples * 2) - - # Create buffer for data - # Use this if CRC16 checksum is required (DATA1-3) - buffer = bytearray(payload_bytes_per_frame) - # set buffer size to length of data which will be sent - buffer[: len(data_out)] = data_out - - # Create CRC for data frame - we are using the CRC function shipped with codec2 to avoid - # CRC algorithm incompatibilities - # generate CRC16 - crc = ctypes.c_ushort( - codec2.api.freedv_gen_crc16(bytes(buffer), payload_bytes_per_frame) - ) - crc = crc.value.to_bytes(2, byteorder="big") # convert crc to 2 byte hex string - buffer += crc # append crc16 to buffer - - print( - f"TOTAL BURSTS: {N_BURSTS} TOTAL FRAMES_PER_BURST: {N_FRAMES_PER_BURST}", - file=sys.stderr, - ) - - for brst in range(1, N_BURSTS + 1): - # Write preamble to txbuffer - codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble) - txbuffer = bytes(mod_out_preamble) - - # Create modulaton for N = FRAMESPERBURST and append it to txbuffer - for frm in range(1, N_FRAMES_PER_BURST + 1): - data = (ctypes.c_ubyte * bytes_per_frame).from_buffer_copy(buffer) - # Modulate DATA and save it into mod_out pointer - codec2.api.freedv_rawdatatx(freedv, mod_out, data) - - txbuffer += bytes(mod_out) - - print( - f"TX BURST: {brst}/{N_BURSTS} FRAME: {frm}/{N_FRAMES_PER_BURST}", - file=sys.stderr, - ) - - # Append postamble to txbuffer - codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble) - txbuffer += bytes(mod_out_postamble) - - # Append a delay between bursts as audio silence - samples_delay = int(MODEM_SAMPLE_RATE * DELAY_BETWEEN_BURSTS) - mod_out_silence = ctypes.create_string_buffer(samples_delay * 2) - txbuffer += bytes(mod_out_silence) - # print(f"samples_delay: {samples_delay} DELAY_BETWEEN_BURSTS: {DELAY_BETWEEN_BURSTS}", file=sys.stderr) - - # Resample up to 48k (resampler works on np.int16) - np_buffer = np.frombuffer(txbuffer, dtype=np.int16) - txbuffer_48k = resampler.resample8_to_48(np_buffer) - - # Check if we want to use an audio device or stdout - if AUDIO_OUTPUT_DEVICE != -1: - stream_tx.start() # type: ignore - stream_tx.write(txbuffer_48k) # type: ignore - else: - # Print data to terminal for piping the output to other programs - sys.stdout.buffer.write(txbuffer_48k) # type: ignore - sys.stdout.flush() - - # and at last check if we had an opened audio instance and close it - if AUDIO_OUTPUT_DEVICE != -1: - sd._terminate() - - -def parse_arguments(): - # GET PARAMETER INPUTS - parser = argparse.ArgumentParser(description="Simons TEST TNC") - parser.add_argument("--bursts", dest="N_BURSTS", default=1, type=int) - parser.add_argument( - "--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int - ) - parser.add_argument( - "--delay", - dest="DELAY_BETWEEN_BURSTS", - default=500, - type=int, - help="delay between bursts in ms", - ) - parser.add_argument( - "--mode", dest="FREEDV_MODE", type=str, choices=["datac13", "datac1", "datac3"] - ) - parser.add_argument( - "--audiodev", - dest="AUDIO_OUTPUT_DEVICE", - default=-1, - type=int, - help="audio output device number to use, use -2 to automatically select a loopback device", - ) - parser.add_argument( - "--list", - dest="LIST", - action="store_true", - help="list audio devices by number and exit", - ) - parser.add_argument( - "--testframes", - dest="TESTFRAMES", - action="store_true", - default=False, - help="list audio devices by number and exit", - ) - - args, _ = parser.parse_known_args() - return args - - -if __name__ == "__main__": - util_tx() diff --git a/modem/lib/libcodec2 b/modem/lib/libcodec2 deleted file mode 160000 index e9d726bf..00000000 --- a/modem/lib/libcodec2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e9d726bf208a64acf64ae996346569d29409bf6e