diff --git a/.github/workflows/build_multiplatform.yml b/.github/workflows/build_multiplatform.yml index 49a7c7756..6ad4d9c02 100644 --- a/.github/workflows/build_multiplatform.yml +++ b/.github/workflows/build_multiplatform.yml @@ -112,6 +112,8 @@ jobs: steps: - uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} - uses: uraimo/run-on-arch-action@v2 name: Build artifact id: build diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 55be8246e..a7c6a4660 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -40,6 +40,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index 23d2c69c2..be9a511a0 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -44,7 +44,7 @@ jobs: shell: bash run: | git clone https://github.com/drowe67/codec2.git - cd codec2 && git checkout master # This should be pinned to a release + cd codec2 mkdir -p build_linux && cd build_linux && cmake .. && make - name: run ctests diff --git a/CMakeLists.txt b/CMakeLists.txt index 86c5f6ec9..08e9be42b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,19 +58,19 @@ add_test(NAME tnc_irs_iss # python3 test_chat_text.py") # set_tests_properties(chat_text PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") -add_test(NAME datac0_frames +add_test(NAME datac13_frames COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; export PYTHONPATH=../tnc; cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 test_datac0.py") - set_tests_properties(datac0_frames PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") + python3 test_datac13.py") + set_tests_properties(datac13_frames PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") -add_test(NAME datac0_frames_negative +add_test(NAME datac13_frames_negative COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; export PYTHONPATH=../tnc; cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 test_datac0_negative.py") - set_tests_properties(datac0_frames_negative PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") + 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; @@ -87,7 +87,7 @@ add_test(NAME py_highsnr_stdio_P_P_multi 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 "DATAC0: ${BURSTS}/${FRAMESPERBURST} DATAC1: ${BURSTS}/${FRAMESPERBURST} DATAC3: ${BURSTS}/${FRAMESPERBURST}") + 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; @@ -124,26 +124,26 @@ 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 datac0 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | + 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 datac0 - - --framesperburst ${FRAMESPERBURST} | hexdump -C") + 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 datac0 --testframes ${TESTFRAMES} --bursts ${BURSTS} --framesperburst ${FRAMESPERBURST} /dev/zero - | + 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 datac0 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") + 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 datac0 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} | - python3 util_rx.py --debug --mode datac0 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}") + 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 @@ -151,8 +151,8 @@ add_test(NAME highsnr_stdio_P_P_multi 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 20") - set_tests_properties(highsnr_stdio_P_P_multi PROPERTIES PASS_REGULAR_EXPRESSION "DATAC0: ${BURSTS}/${FRAMESPERBURST} DATAC1: ${BURSTS}/${FRAMESPERBURST} DATAC3: ${BURSTS}/${FRAMESPERBURST}") + 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}) @@ -179,7 +179,7 @@ add_test(NAME highsnr_virtual3_P_P_multi 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 "DATAC0: 2/4 DATAC1: 2/4 DATAC3: 2/4") + 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 @@ -203,7 +203,7 @@ add_test(NAME highsnr_virtual5_P_P_multi_callback 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 "DATAC0: 2/4 DATAC1: 2/4 DATAC3: 2/4") + 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 @@ -211,7 +211,7 @@ add_test(NAME highsnr_virtual5_P_P_multi_callback_outside 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 "DATAC0: 2/4 DATAC1: 2/4 DATAC3: 2/4") + 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/gui/preload-chat.js b/gui/preload-chat.js index 7cf514e35..b4243c48a 100644 --- a/gui/preload-chat.js +++ b/gui/preload-chat.js @@ -483,7 +483,7 @@ window.addEventListener("DOMContentLoaded", () => { command: "msg", dxcallsign: dxcallsign, mode: 255, - frames: 1, + frames: 5, data: data_with_attachment, checksum: file_checksum, uuid: uuid, @@ -1443,7 +1443,7 @@ update_chat = function (obj) { command: "msg", dxcallsign: doc.dxcallsign, mode: 255, - frames: 1, + frames: 5, data: data_with_attachment, checksum: doc.checksum, uuid: doc.uuid, diff --git a/gui/preload-main.js b/gui/preload-main.js index 4e237b984..a67501fd4 100644 --- a/gui/preload-main.js +++ b/gui/preload-main.js @@ -1612,6 +1612,7 @@ ipcRenderer.on("action-update-reception-status", (event, arg) => { var time_left = "" + time_left + " || Speed/min: "; // SET BYTES PER MINUTE + if (typeof data.bytesperminute == "undefined") { var arq_bytes_per_minute = 0; } else { @@ -1626,7 +1627,7 @@ ipcRenderer.on("action-update-reception-status", (event, arg) => { var arq_bytes_per_minute_compressed = Math.round( arq_bytes_per_minute * compress ); - + console.log(arq_bytes_per_minute); time_left += formatBytes(arq_bytes_per_minute, 1) + " (comp: " + @@ -2188,11 +2189,14 @@ function updateHeardStations(arg) { //https://stackoverflow.com/a/847196 timestampRaw = arg.stations[i]["timestamp"]; - var date = new Date(timestampRaw * 1000); - var hours = date.getHours(); - var minutes = "0" + date.getMinutes(); - var seconds = "0" + date.getSeconds(); - var datetime = hours + ":" + minutes.substr(-2) + ":" + seconds.substr(-2); + + var datetime = new Date(timestampRaw * 1000).toLocaleString( + navigator.language + ); + //var hours = date.getHours(); + //var minutes = "0" + date.getMinutes(); + //var seconds = "0" + date.getSeconds(); + //var datetime = hours + ":" + minutes.substr(-2) + ":" + seconds.substr(-2); var timestamp = document.createElement("td"); var timestampText = document.createElement("span"); diff --git a/gui/sock.js b/gui/sock.js index 11ff5eadd..9a938f810 100644 --- a/gui/sock.js +++ b/gui/sock.js @@ -198,6 +198,7 @@ client.on("data", function (socketdata) { dbfs_level: data["audio_dbfs"], fft: data["fft"], channel_busy: data["channel_busy"], + channel_busy_slot: data["channel_busy_slot"], scatter: data["scatter"], info: data["info"], rx_buffer_length: data["rx_buffer_length"], diff --git a/test/001_highsnr_stdio_audio/test_virtual3.sh b/test/001_highsnr_stdio_audio/test_virtual3.sh index 50b68e3ae..e49f978f3 100755 --- a/test/001_highsnr_stdio_audio/test_virtual3.sh +++ b/test/001_highsnr_stdio_audio/test_virtual3.sh @@ -16,8 +16,8 @@ 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 datac0 --frames 2 --bursts 3 --audiodev -2 --debug & +python3 util_callback_rx.py --mode datac13 --frames 2 --bursts 3 --audiodev -2 --debug & rx_pid=$! #sleep 1 -python3 util_tx.py --mode datac0 --frames 2 --bursts 3 --audiodev -2 +python3 util_tx.py --mode datac13 --frames 2 --bursts 3 --audiodev -2 wait ${rx_pid} diff --git a/test/ping.py b/test/ping.py index 0446dd595..01bf3e622 100644 --- a/test/ping.py +++ b/test/ping.py @@ -204,7 +204,7 @@ def receive(): # time.sleep(DELAY_BETWEEN_BURSTS) - # WAIT UNTIL WE RECEIVD AN ACK/DATAC0 FRAME + # WAIT UNTIL WE RECEIVD AN ACK/datac13 FRAME while ACK_TIMEOUT >= time.time(): time.sleep(0.01) diff --git a/test/test_datac0.py b/test/test_datac13.py similarity index 93% rename from test/test_datac0.py rename to test/test_datac13.py index 9ff943926..4cb3131bb 100644 --- a/test/test_datac0.py +++ b/test/test_datac13.py @@ -9,7 +9,7 @@ Can be invoked from CMake, pytest, coverage or directly. -Uses util_datac0.py in separate process to perform the data transfer. +Uses util_datac13.py in separate process to perform the data transfer. @author: N2KIQ """ @@ -28,9 +28,9 @@ import structlog try: - import test.util_datac0 as util + import test.util_datac13 as util except ImportError: - import util_datac0 as util + import util_datac13 as util STATIONS = ["AA2BB", "ZZ9YY"] @@ -48,11 +48,11 @@ def parameters() -> dict: connect_data = {"type": "arq", "command": "connect", "dxcallsign": "ZZ9YY-0"} stop_data = {"type": "arq", "command": "stop_transmission", "dxcallsign": "ZZ9YY-0"} - beacon_timeout = 6 - cq_timeout = 8 - ping_timeout = 5 - connect_timeout = 10 - stop_timeout = 5 + 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"' @@ -192,13 +192,13 @@ def analyze_results(station1: list, station2: list, call_list: list): pytest.param("beacon", marks=pytest.mark.flaky(reruns=2)), pytest.param("ping", marks=pytest.mark.flaky(reruns=2)), 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=0)), + #pytest.param("cq", marks=pytest.mark.xfail(reason="Too unstable for CI")), + pytest.param("stop", marks=pytest.mark.flaky(reruns=2)), ], ) -def test_datac0(frame_type: str, tmp_path): - log_handler.setup_logging(filename=tmp_path / "test_datac0", level="DEBUG") - log = structlog.get_logger("test_datac0") +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 = [] @@ -227,7 +227,7 @@ def recv_from_pipes(s1_rx, s1_pipe, s2_rx, s2_pipe) -> list: from_s2, s2_send = multiprocessing.Pipe() proc = [ multiprocessing.Process( - target=util.t_datac0_1, + target=util.t_datac13_1, args=( s1_send, STATIONS[0], @@ -238,7 +238,7 @@ def recv_from_pipes(s1_rx, s1_pipe, s2_rx, s2_pipe) -> list: daemon=True, ), multiprocessing.Process( - target=util.t_datac0_2, + target=util.t_datac13_2, args=( s2_send, STATIONS[1], diff --git a/test/test_datac0_negative.py b/test/test_datac13_negative.py similarity index 93% rename from test/test_datac0_negative.py rename to test/test_datac13_negative.py index d90fb6f0c..699fd487f 100644 --- a/test/test_datac0_negative.py +++ b/test/test_datac13_negative.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -Negative tests for datac0 frames. +Negative tests for datac13 frames. @author: kronenpj """ @@ -20,9 +20,9 @@ import structlog try: - import test.util_datac0_negative as util + import test.util_datac13_negative as util except ImportError: - import util_datac0_negative as util + import util_datac13_negative as util STATIONS = ["AA2BB", "ZZ9YY"] @@ -162,10 +162,13 @@ def analyze_results(station1: list, station2: list, call_list: list): # @pytest.mark.parametrize("frame_type", ["beacon", "connect", "ping"]) -@pytest.mark.parametrize("frame_type", ["ping", "stop"]) -def test_datac0_negative(frame_type: str, tmp_path): - log_handler.setup_logging(filename=tmp_path / "test_datac0", level="DEBUG") - log = structlog.get_logger("test_datac0") +@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 = [] @@ -194,7 +197,7 @@ def recv_from_pipes(s1_rx, s1_pipe, s2_rx, s2_pipe) -> list: from_s2, s2_send = multiprocessing.Pipe() proc = [ multiprocessing.Process( - target=util.t_datac0_1, + target=util.t_datac13_1, args=( s1_send, STATIONS[0], @@ -205,7 +208,7 @@ def recv_from_pipes(s1_rx, s1_pipe, s2_rx, s2_pipe) -> list: daemon=True, ), multiprocessing.Process( - target=util.t_datac0_2, + target=util.t_datac13_2, args=( s2_send, STATIONS[1], diff --git a/test/test_helpers.py b/test/test_helpers.py index 49186b7a1..7fc8d3db6 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -14,7 +14,7 @@ import helpers import pytest -import static +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC @pytest.mark.parametrize("callsign", ["AA1AA", "DE2DE", "E4AWQ-4"]) @@ -22,7 +22,7 @@ def test_check_callsign(callsign: str): """ Execute test to demonstrate how to create and verify callsign checksums. """ - static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + 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) @@ -41,7 +41,7 @@ def test_callsign_to_bytes(callsign: str): """ Execute test to demonsrate symmetry when converting callsigns to and from byte arrays. """ - static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + 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) diff --git a/test/test_highsnr_stdio_C_P_datacx.py b/test/test_highsnr_stdio_C_P_datacx.py index 21c9c7098..aa095cd68 100644 --- a/test/test_highsnr_stdio_C_P_datacx.py +++ b/test/test_highsnr_stdio_C_P_datacx.py @@ -49,7 +49,7 @@ 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 DATAC0. + Test a high signal-to-noise ratio path with datac13. :param bursts: Number of bursts :type bursts: str @@ -152,7 +152,7 @@ def t_HighSNR_C_P_DATACx( @pytest.mark.parametrize("bursts", [BURSTS]) @pytest.mark.parametrize("frames_per_burst", [FRAMESPERBURST]) @pytest.mark.parametrize("testframes", [TESTFRAMES]) -@pytest.mark.parametrize("mode", ["datac0", "datac1", "datac3"]) +@pytest.mark.parametrize("mode", ["datac13", "datac1", "datac3"]) def test_HighSNR_C_P_DATACx( bursts: int, frames_per_burst: int, testframes: int, mode: str ): diff --git a/test/test_highsnr_stdio_P_C_datacx.py b/test/test_highsnr_stdio_P_C_datacx.py index f213def4f..5574636df 100644 --- a/test/test_highsnr_stdio_P_C_datacx.py +++ b/test/test_highsnr_stdio_P_C_datacx.py @@ -47,7 +47,7 @@ def t_HighSNR_P_C_DATACx(bursts: int, frames_per_burst: int, mode: str): """ - Test a high signal-to-noise ratio path with DATAC0. + Test a high signal-to-noise ratio path with datac13. :param bursts: Number of bursts :type bursts: str @@ -154,7 +154,7 @@ def t_HighSNR_P_C_DATACx(bursts: int, frames_per_burst: int, mode: str): # @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", ["datac0", "datac1", "datac3"]) +@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, diff --git a/test/test_highsnr_stdio_P_P_datacx.py b/test/test_highsnr_stdio_P_P_datacx.py index 78aaff327..d63aa1e26 100644 --- a/test/test_highsnr_stdio_P_P_datacx.py +++ b/test/test_highsnr_stdio_P_P_datacx.py @@ -115,7 +115,7 @@ def t_HighSNR_P_P_DATACx(bursts: int, frames_per_burst: int, mode: str): # @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", ["datac0", "datac1", "datac3"]) +@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, diff --git a/test/test_highsnr_stdio_P_P_multi.py b/test/test_highsnr_stdio_P_P_multi.py index 7ce5b1a54..ebf2bacf3 100644 --- a/test/test_highsnr_stdio_P_P_multi.py +++ b/test/test_highsnr_stdio_P_P_multi.py @@ -46,7 +46,7 @@ def t_HighSNR_P_P_Multi(bursts: int, frames_per_burst: int): """ - Test a high signal-to-noise ratio path with DATAC0, DATAC1 and DATAC3. + Test a high signal-to-noise ratio path with datac13, DATAC1 and DATAC3. :param bursts: Number of bursts :type bursts: int @@ -101,7 +101,7 @@ def t_HighSNR_P_P_Multi(bursts: int, frames_per_burst: int): if "DATAC" in str(line, "UTF-8") ] ) - assert f"DATAC0: {bursts}/{frames_per_burst * bursts}" in lastline + 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) diff --git a/test/test_tnc_states.py b/test/test_tnc_states.py index aa7a10dc0..d4aab6dc5 100644 --- a/test/test_tnc_states.py +++ b/test/test_tnc_states.py @@ -9,7 +9,7 @@ Can be invoked from CMake, pytest, coverage or directly. -Uses util_datac0.py in separate process to perform the data transfer. +Uses util_datac13.py in separate process to perform the data transfer. @author: N2KIQ """ @@ -26,7 +26,7 @@ sys.path.insert(0, "../tnc") import data_handler import helpers -import static +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC def print_frame(data: bytearray): @@ -148,18 +148,18 @@ def t_foreign_disconnect(mycall: str, dxcall: str): :rtype: bytearray """ # Set the SSIDs we'll use for this test. - static.SSID_LIST = [0, 1, 2, 3, 4] + 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) - static.MYCALLSIGN = mycallsign - static.MYCALLSIGN_CRC = helpers.get_crc_24(mycallsign) + 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) - static.DXCALLSIGN = dxcallsign - static.DXCALLSIGN_CRC = helpers.get_crc_24(dxcallsign) + Station.dxcallsign = dxcallsign + Station.dxcallsign_crc = helpers.get_crc_24(dxcallsign) # Create the TNC tnc = data_handler.DATA() @@ -173,12 +173,12 @@ def t_foreign_disconnect(mycall: str, dxcall: str): print_frame(create_frame) tnc.received_session_opener(create_frame) - assert helpers.callsign_to_bytes(static.MYCALLSIGN) == mycallsign_bytes - assert helpers.callsign_to_bytes(static.DXCALLSIGN) == dxcallsign_bytes + assert helpers.callsign_to_bytes(Station.mycallsign) == mycallsign_bytes + assert helpers.callsign_to_bytes(Station.dxcallsign) == dxcallsign_bytes - assert static.ARQ_SESSION is True - assert static.TNC_STATE == "BUSY" - assert static.ARQ_SESSION_STATE == "connecting" + assert ARQ.arq_session is True + assert TNC.tnc_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") @@ -193,8 +193,8 @@ def t_foreign_disconnect(mycall: str, dxcall: str): print_frame(close_frame) # assert ( - # helpers.check_callsign(static.DXCALLSIGN, bytes(close_frame[4:7]))[0] is False - # ), f"{helpers.get_crc_24(static.DXCALLSIGN)} == {bytes(close_frame[4:7])} but should be not equal." + # 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 @@ -203,16 +203,16 @@ def t_foreign_disconnect(mycall: str, dxcall: str): # Send the non-associated session close frame to the TNC tnc.received_session_close(close_frame) - assert helpers.callsign_to_bytes(static.MYCALLSIGN) == helpers.callsign_to_bytes( + assert helpers.callsign_to_bytes(Station.mycallsign) == helpers.callsign_to_bytes( mycall - ), f"{static.MYCALLSIGN} != {mycall} but should equal." - assert helpers.callsign_to_bytes(static.DXCALLSIGN) == helpers.callsign_to_bytes( + ), f"{Station.mycallsign} != {mycall} but should equal." + assert helpers.callsign_to_bytes(Station.dxcallsign) == helpers.callsign_to_bytes( dxcall - ), f"{static.DXCALLSIGN} != {dxcall} but should equal." + ), f"{Station.dxcallsign} != {dxcall} but should equal." - assert static.ARQ_SESSION is True - assert static.TNC_STATE == "BUSY" - assert static.ARQ_SESSION_STATE == "connecting" + assert ARQ.arq_session is True + assert TNC.tnc_state == "BUSY" + assert ARQ.arq_session_state == "connecting" def t_valid_disconnect(mycall: str, dxcall: str): @@ -228,18 +228,18 @@ def t_valid_disconnect(mycall: str, dxcall: str): :rtype: bytearray """ # Set the SSIDs we'll use for this test. - static.SSID_LIST = [0, 1, 2, 3, 4] + 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) - static.MYCALLSIGN = mycallsign - static.MYCALLSIGN_CRC = helpers.get_crc_24(mycallsign) + 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) - static.DXCALLSIGN = dxcallsign - static.DXCALLSIGN_CRC = helpers.get_crc_24(dxcallsign) + Station.dxcallsign = dxcallsign + Station.dxcallsign_crc = helpers.get_crc_24(dxcallsign) # Create the TNC tnc = data_handler.DATA() @@ -253,9 +253,9 @@ def t_valid_disconnect(mycall: str, dxcall: str): print_frame(create_frame) tnc.received_session_opener(create_frame) - assert static.ARQ_SESSION is True - assert static.TNC_STATE == "BUSY" - assert static.ARQ_SESSION_STATE == "connecting" + assert ARQ.arq_session is True + assert TNC.tnc_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) @@ -267,12 +267,12 @@ def t_valid_disconnect(mycall: str, dxcall: str): print_frame(close_frame) tnc.received_session_close(close_frame) - assert helpers.callsign_to_bytes(static.MYCALLSIGN) == mycallsign_bytes - assert helpers.callsign_to_bytes(static.DXCALLSIGN) == dxcallsign_bytes + assert helpers.callsign_to_bytes(Station.mycallsign) == mycallsign_bytes + assert helpers.callsign_to_bytes(Station.dxcallsign) == dxcallsign_bytes - assert static.ARQ_SESSION is False - assert static.TNC_STATE == "IDLE" - assert static.ARQ_SESSION_STATE == "disconnected" + assert ARQ.arq_session is False + assert TNC.tnc_state == "IDLE" + assert ARQ.arq_session_state == "disconnected" # These tests are pushed into separate processes as a workaround. These tests diff --git a/test/test_virtual1.sh b/test/test_virtual1.sh index 18af9813b..d26f9718b 100755 --- a/test/test_virtual1.sh +++ b/test/test_virtual1.sh @@ -15,13 +15,13 @@ function check_alsa_loopback { check_alsa_loopback RX_LOG=$(mktemp) -MAX_RUN_TIME=2600 +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 datac0 --frames 2 --bursts 5 --debug & +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 datac0 --frames 2 --bursts 5 --delay 500 | aplay --device="plughw:CARD=CHAT2,DEV=1" -r 48000 -f S16_LE +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/test/test_virtual1a.sh b/test/test_virtual1a.sh index 402e45070..af528dc9b 100755 --- a/test/test_virtual1a.sh +++ b/test/test_virtual1a.sh @@ -8,9 +8,9 @@ MAX_RUN_TIME=2600 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 datac0 --frames 2 --bursts 5 --debug & + python3 util_rx.py --mode datac13 --frames 2 --bursts 5 --debug & rx_pid=$! sleep 1 -python3 util_tx.py --mode datac0 --frames 2 --bursts 5 --delay 500 | \ +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/test/test_virtual1b.sh b/test/test_virtual1b.sh index 140803c3b..e38f8e920 100755 --- a/test/test_virtual1b.sh +++ b/test/test_virtual1b.sh @@ -8,8 +8,8 @@ MAX_RUN_TIME=2600 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 datac0 --frames 2 --bursts 5 --debug --timeout 20 & + python3 util_rx.py --mode datac13 --frames 2 --bursts 5 --debug --timeout 20 & rx_pid=$! sleep 1 -python3 util_tx.py --mode datac0 --frames 2 --bursts 5 --delay 2000 --audiodev -2 +python3 util_tx.py --mode datac13 --frames 2 --bursts 5 --delay 2000 --audiodev -2 wait ${rx_pid} diff --git a/test/test_virtual1c.sh b/test/test_virtual1c.sh index 3f2835b4a..c432e08bf 100755 --- a/test/test_virtual1c.sh +++ b/test/test_virtual1c.sh @@ -7,9 +7,9 @@ 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 datac0 --frames 2 --bursts 5 --debug --audiodev -2 & +python3 util_rx.py --mode datac13 --frames 2 --bursts 5 --debug --audiodev -2 & rx_pid=$! sleep 1 -python3 util_tx.py --mode datac0 --frames 2 --bursts 5 | \ +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/test/test_virtual2.sh b/test/test_virtual2.sh index 0357cd602..7eec499c4 100755 --- a/test/test_virtual2.sh +++ b/test/test_virtual2.sh @@ -16,8 +16,8 @@ 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 datac0 --frames 2 --bursts 3 --audiodev -2 --debug & +python3 util_callback_rx.py --mode datac13 --frames 2 --bursts 3 --audiodev -2 --debug & rx_pid=$! sleep 1 -python3 util_tx.py --mode datac0 --frames 2 --bursts 3 --audiodev -2 +python3 util_tx.py --mode datac13 --frames 2 --bursts 3 --audiodev -2 wait ${rx_pid} diff --git a/test/test_virtual3a.sh b/test/test_virtual3a.sh index 50b68e3ae..e49f978f3 100755 --- a/test/test_virtual3a.sh +++ b/test/test_virtual3a.sh @@ -16,8 +16,8 @@ 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 datac0 --frames 2 --bursts 3 --audiodev -2 --debug & +python3 util_callback_rx.py --mode datac13 --frames 2 --bursts 3 --audiodev -2 --debug & rx_pid=$! #sleep 1 -python3 util_tx.py --mode datac0 --frames 2 --bursts 3 --audiodev -2 +python3 util_tx.py --mode datac13 --frames 2 --bursts 3 --audiodev -2 wait ${rx_pid} diff --git a/test/test_virtual3b.sh b/test/test_virtual3b.sh index 8474adc51..e6fcc5a1c 100755 --- a/test/test_virtual3b.sh +++ b/test/test_virtual3b.sh @@ -16,8 +16,8 @@ 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 datac0 --frames 2 --bursts 3 --audiodev -2 --debug & +python3 util_callback_rx_outside.py --mode datac13 --frames 2 --bursts 3 --audiodev -2 --debug & rx_pid=$! #sleep 1 -python3 util_tx.py --mode datac0 --frames 2 --bursts 3 --audiodev -2 +python3 util_tx.py --mode datac13 --frames 2 --bursts 3 --audiodev -2 wait ${rx_pid} diff --git a/test/util_callback_multimode_rx.py b/test/util_callback_multimode_rx.py index 248c056c2..3ef2d35c2 100644 --- a/test/util_callback_multimode_rx.py +++ b/test/util_callback_multimode_rx.py @@ -112,20 +112,20 @@ def __init__(self): sys.exit() # open codec2 instance - self.datac0_freedv = ctypes.cast( - codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), ctypes.c_void_p + self.datac13_freedv = ctypes.cast( + codec2.api.freedv_open(codec2.FREEDV_MODE.datac13.value), ctypes.c_void_p ) - self.datac0_bytes_per_frame = int( - codec2.api.freedv_get_bits_per_modem_frame(self.datac0_freedv) / 8 + self.datac13_bytes_per_frame = int( + codec2.api.freedv_get_bits_per_modem_frame(self.datac13_freedv) / 8 ) - self.datac0_bytes_out = ctypes.create_string_buffer(self.datac0_bytes_per_frame) + self.datac13_bytes_out = ctypes.create_string_buffer(self.datac13_bytes_per_frame) codec2.api.freedv_set_frames_per_burst( - self.datac0_freedv, self.N_FRAMES_PER_BURST + self.datac13_freedv, self.N_FRAMES_PER_BURST ) - self.datac0_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER) + self.datac13_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER) self.datac1_freedv = ctypes.cast( - codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), ctypes.c_void_p + 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 @@ -137,7 +137,7 @@ def __init__(self): self.datac1_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER) self.datac3_freedv = ctypes.cast( - codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), ctypes.c_void_p + 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 @@ -149,9 +149,9 @@ def __init__(self): self.datac3_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER) # SET COUNTERS - self.rx_total_frames_datac0 = 0 - self.rx_frames_datac0 = 0 - self.rx_bursts_datac0 = 0 + 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 @@ -173,7 +173,7 @@ def __init__(self): self.frx = open("rx48_callback_multimode.raw", mode="wb") # initial nin values - self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv) + 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) @@ -187,26 +187,26 @@ def callback(self, data_in48k, frame_count, time_info, status): x.tofile(self.frx) x = self.resampler.resample48_to_8(x) - self.datac0_buffer.push(x) + self.datac13_buffer.push(x) self.datac1_buffer.push(x) self.datac3_buffer.push(x) - while self.datac0_buffer.nbuffer >= self.datac0_nin: + while self.datac13_buffer.nbuffer >= self.datac13_nin: # demodulate audio nbytes = codec2.api.freedv_rawdatarx( - self.datac0_freedv, - self.datac0_bytes_out, - self.datac0_buffer.buffer.ctypes, + self.datac13_freedv, + self.datac13_bytes_out, + self.datac13_buffer.buffer.ctypes, ) - self.datac0_buffer.pop(self.datac0_nin) - self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv) - if nbytes == self.datac0_bytes_per_frame: - self.rx_total_frames_datac0 = self.rx_total_frames_datac0 + 1 - self.rx_frames_datac0 = self.rx_frames_datac0 + 1 + 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_datac0 == self.N_FRAMES_PER_BURST: - self.rx_frames_datac0 = 0 - self.rx_bursts_datac0 = self.rx_bursts_datac0 + 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 @@ -243,7 +243,7 @@ def callback(self, data_in48k, frame_count, time_info, status): self.rx_bursts_datac3 = self.rx_bursts_datac3 + 1 if ( - self.rx_bursts_datac0 and self.rx_bursts_datac1 and self.rx_bursts_datac3 + self.rx_bursts_datac13 and self.rx_bursts_datac1 and self.rx_bursts_datac3 ) == self.N_BURSTS: self.receive = False @@ -253,11 +253,11 @@ def print_stats(self): while self.receive: time.sleep(0.01) if self.DEBUGGING_MODE: - self.datac0_rxstatus = codec2.api.freedv_get_rx_status( - self.datac0_freedv + self.datac13_rxstatus = codec2.api.freedv_get_rx_status( + self.datac13_freedv ) - self.datac0_rxstatus = codec2.api.rx_sync_flags_to_text[ - self.datac0_rxstatus + self.datac13_rxstatus = codec2.api.rx_sync_flags_to_text[ + self.datac13_rxstatus ] self.datac1_rxstatus = codec2.api.freedv_get_rx_status( @@ -277,8 +277,8 @@ def print_stats(self): print( "NIN0: %5d RX_STATUS0: %4s NIN1: %5d RX_STATUS1: %4s NIN3: %5d RX_STATUS3: %4s" % ( - self.datac0_nin, - self.datac0_rxstatus, + self.datac13_nin, + self.datac13_rxstatus, self.datac1_nin, self.datac1_rxstatus, self.datac3_nin, @@ -309,7 +309,7 @@ def run_audio(self): ) print( - f"DATAC0: {self.rx_bursts_datac0}/{self.rx_total_frames_datac0} DATAC1: {self.rx_bursts_datac1}/{self.rx_total_frames_datac1} DATAC3: {self.rx_bursts_datac3}/{self.rx_total_frames_datac3}", + 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() diff --git a/test/util_callback_multimode_rx_outside.py b/test/util_callback_multimode_rx_outside.py index decedefe2..eb4d89527 100644 --- a/test/util_callback_multimode_rx_outside.py +++ b/test/util_callback_multimode_rx_outside.py @@ -110,20 +110,20 @@ def __init__(self): sys.exit() # open codec2 instance - self.datac0_freedv = ctypes.cast( - codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), ctypes.c_void_p + self.datac13_freedv = ctypes.cast( + codec2.api.freedv_open(codec2.FREEDV_MODE.datac13.value), ctypes.c_void_p ) - self.datac0_bytes_per_frame = int( - codec2.api.freedv_get_bits_per_modem_frame(self.datac0_freedv) / 8 + self.datac13_bytes_per_frame = int( + codec2.api.freedv_get_bits_per_modem_frame(self.datac13_freedv) / 8 ) - self.datac0_bytes_out = ctypes.create_string_buffer(self.datac0_bytes_per_frame) + self.datac13_bytes_out = ctypes.create_string_buffer(self.datac13_bytes_per_frame) codec2.api.freedv_set_frames_per_burst( - self.datac0_freedv, self.N_FRAMES_PER_BURST + self.datac13_freedv, self.N_FRAMES_PER_BURST ) - self.datac0_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER) + self.datac13_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER) self.datac1_freedv = ctypes.cast( - codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), ctypes.c_void_p + 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 @@ -135,7 +135,7 @@ def __init__(self): self.datac1_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER) self.datac3_freedv = ctypes.cast( - codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), ctypes.c_void_p + 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 @@ -147,9 +147,9 @@ def __init__(self): self.datac3_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER) # SET COUNTERS - self.rx_total_frames_datac0 = 0 - self.rx_frames_datac0 = 0 - self.rx_bursts_datac0 = 0 + 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 @@ -171,7 +171,7 @@ def __init__(self): self.frx = open("rx48_callback_multimode.raw", mode="wb") # initial nin values - self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv) + 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) @@ -181,7 +181,7 @@ def callback(self, data_in48k, frame_count, time_info, status): x.tofile(self.frx) x = self.resampler.resample48_to_8(x) - self.datac0_buffer.push(x) + self.datac13_buffer.push(x) self.datac1_buffer.push(x) self.datac3_buffer.push(x) @@ -189,9 +189,9 @@ def callback(self, data_in48k, frame_count, time_info, status): def print_stats(self): if self.DEBUGGING_MODE: - self.datac0_rxstatus = codec2.api.freedv_get_rx_status(self.datac0_freedv) - self.datac0_rxstatus = codec2.api.rx_sync_flags_to_text[ - self.datac0_rxstatus + 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) @@ -207,8 +207,8 @@ def print_stats(self): print( "NIN0: %5d RX_STATUS0: %4s NIN1: %5d RX_STATUS1: %4s NIN3: %5d RX_STATUS3: %4s" % ( - self.datac0_nin, - self.datac0_rxstatus, + self.datac13_nin, + self.datac13_rxstatus, self.datac1_nin, self.datac1_rxstatus, self.datac3_nin, @@ -225,22 +225,22 @@ def run_audio(self): print(f"pyAudio error: {e}", file=sys.stderr) while self.receive and time.time() < self.timeout: - while self.datac0_buffer.nbuffer >= self.datac0_nin: + while self.datac13_buffer.nbuffer >= self.datac13_nin: # demodulate audio nbytes = codec2.api.freedv_rawdatarx( - self.datac0_freedv, - self.datac0_bytes_out, - self.datac0_buffer.buffer.ctypes, + self.datac13_freedv, + self.datac13_bytes_out, + self.datac13_buffer.buffer.ctypes, ) - self.datac0_buffer.pop(self.datac0_nin) - self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv) - if nbytes == self.datac0_bytes_per_frame: - self.rx_total_frames_datac0 = self.rx_total_frames_datac0 + 1 - self.rx_frames_datac0 = self.rx_frames_datac0 + 1 - - if self.rx_frames_datac0 == self.N_FRAMES_PER_BURST: - self.rx_frames_datac0 = 0 - self.rx_bursts_datac0 = self.rx_bursts_datac0 + 1 + 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: @@ -280,7 +280,7 @@ def run_audio(self): self.print_stats() if ( - self.rx_bursts_datac0 + self.rx_bursts_datac13 and self.rx_bursts_datac1 and self.rx_bursts_datac3 ) == self.N_BURSTS: @@ -297,7 +297,7 @@ def run_audio(self): ) print( - f"DATAC0: {self.rx_bursts_datac0}/{self.rx_total_frames_datac0} DATAC1: {self.rx_bursts_datac1}/{self.rx_total_frames_datac1} DATAC3: {self.rx_bursts_datac3}/{self.rx_total_frames_datac3}", + 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() diff --git a/test/util_callback_multimode_tx.py b/test/util_callback_multimode_tx.py index 89eee0ae8..79e302849 100644 --- a/test/util_callback_multimode_tx.py +++ b/test/util_callback_multimode_tx.py @@ -158,9 +158,9 @@ def run_audio(self): def create_modulation(self): modes = [ - codec2.api.FREEDV_MODE_DATAC0, - codec2.api.FREEDV_MODE_DATAC1, - codec2.api.FREEDV_MODE_DATAC3, + codec2.FREEDV_MODE.datac13.value, + codec2.FREEDV_MODE.datac1.value, + codec2.FREEDV_MODE.datac3.value, ] for m in modes: diff --git a/test/util_callback_rx.py b/test/util_callback_rx.py index 0d0bf01bb..0a7237b18 100644 --- a/test/util_callback_rx.py +++ b/test/util_callback_rx.py @@ -22,7 +22,7 @@ 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=["datac0", "datac1", "datac3"] + "--mode", dest="FREEDV_MODE", type=str, choices=["datac13", "datac1", "datac3"] ) parser.add_argument( "--audiodev", @@ -35,7 +35,7 @@ parser.add_argument( "--timeout", dest="TIMEOUT", - default=10, + default=60, type=int, help="Timeout (seconds) before test ends", ) diff --git a/test/util_callback_rx_outside.py b/test/util_callback_rx_outside.py index e9ec63173..dc1fc3ce0 100644 --- a/test/util_callback_rx_outside.py +++ b/test/util_callback_rx_outside.py @@ -22,7 +22,7 @@ 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=["datac0", "datac1", "datac3"] + "--mode", dest="FREEDV_MODE", type=str, choices=["datac13", "datac1", "datac3"] ) parser.add_argument( "--audiodev", diff --git a/test/util_callback_tx.py b/test/util_callback_tx.py index 42d8bf0c3..3991a71f6 100644 --- a/test/util_callback_tx.py +++ b/test/util_callback_tx.py @@ -25,7 +25,7 @@ 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=["datac0", "datac1", "datac3"] + "--mode", dest="FREEDV_MODE", type=str, choices=["datac13", "datac1", "datac3"] ) parser.add_argument( "--audiodev", diff --git a/test/util_chat_text_1.py b/test/util_chat_text_1.py index d89dd3647..444f5460d 100644 --- a/test/util_chat_text_1.py +++ b/test/util_chat_text_1.py @@ -22,7 +22,7 @@ import helpers import modem import sock -import static +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC import structlog @@ -39,23 +39,23 @@ def t_setup( modem.RXCHANNEL = tmp_path / "hfchannel1" modem.TESTMODE = True modem.TXCHANNEL = tmp_path / "hfchannel2" - static.HAMLIB_RADIOCONTROL = "disabled" - static.LOW_BANDWIDTH_MODE = lowbwmode - static.MYGRID = bytes("AA12aa", "utf-8") - static.RESPOND_TO_CQ = True - static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + HamlibParam.hamlib_radiocontrol = "disabled" + TNC.low_bandwidth_mode = lowbwmode + Station.mygrid = bytes("AA12aa", "utf-8") + TNC.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 - static.ARQ_SESSION_STATE = "connected" + ARQ.arq_session_state = "connected" mycallsign = helpers.callsign_to_bytes(mycall) mycallsign = helpers.bytes_to_callsign(mycallsign) - static.MYCALLSIGN = mycallsign - static.MYCALLSIGN_CRC = helpers.get_crc_24(static.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) - static.DXCALLSIGN = dxcallsign - static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN) + Station.dxcallsign = dxcallsign + Station.dxcallsign_crc = helpers.get_crc_24(Station.dxcallsign) # Create the TNC tnc = data_handler.DATA() @@ -105,7 +105,7 @@ def t_transmit(self, mode, repeats: int, repeat_delay: int, frames: bytearray): log.info("S1 TX: ", mode=static.FRAME_TYPE(frametype).name) if ( - static.LOW_BANDWIDTH_MODE + TNC.low_bandwidth_mode and frametype == static.FRAME_TYPE.ARQ_DC_OPEN_W.value ): mesg = ( @@ -116,7 +116,7 @@ def t_transmit(self, mode, repeats: int, repeat_delay: int, frames: bytearray): log.error(mesg) assert False, mesg if ( - not static.LOW_BANDWIDTH_MODE + not TNC.low_bandwidth_mode and frametype == static.FRAME_TYPE.ARQ_DC_OPEN_N.value ): mesg = ( @@ -184,23 +184,23 @@ def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): log.warning("station1 TIMEOUT", first=True) break time.sleep(0.1) - log.info("station1, first", arq_state=pformat(static.ARQ_STATE)) + log.info("station1, first", arq_state=pformat(ARQ.arq_state)) data = {"type": "arq", "command": "disconnect", "dxcallsign": dxcall} sock.process_tnc_commands(json.dumps(data, indent=None)) time.sleep(0.5) # override ARQ SESSION STATE for allowing disconnect command - static.ARQ_SESSION_STATE = "connected" + ARQ.arq_session_state = "connected" sock.process_tnc_commands(json.dumps(data, indent=None)) # Allow enough time for this side to process the disconnect frame. timeout = time.time() + 20 - while static.ARQ_STATE or tnc.data_queue_transmit.queue: + while ARQ.arq_state or tnc.data_queue_transmit.queue: if time.time() > timeout: log.error("station1", TIMEOUT=True) break time.sleep(0.5) - log.info("station1", arq_state=pformat(static.ARQ_STATE)) + log.info("station1", arq_state=pformat(ARQ.arq_state)) # log.info("S1 DQT: ", DQ_Tx=pformat(tnc.data_queue_transmit.queue)) # log.info("S1 DQR: ", DQ_Rx=pformat(tnc.data_queue_received.queue)) diff --git a/test/util_chat_text_2.py b/test/util_chat_text_2.py index 72f44fceb..77f2523b3 100644 --- a/test/util_chat_text_2.py +++ b/test/util_chat_text_2.py @@ -19,7 +19,7 @@ import helpers import modem import sock -import static +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC import structlog @@ -36,23 +36,23 @@ def t_setup( modem.RXCHANNEL = tmp_path / "hfchannel2" modem.TESTMODE = True modem.TXCHANNEL = tmp_path / "hfchannel1" - static.HAMLIB_RADIOCONTROL = "disabled" - static.LOW_BANDWIDTH_MODE = lowbwmode - static.MYGRID = bytes("AA12aa", "utf-8") - static.RESPOND_TO_CQ = True - static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + HamlibParam.hamlib_radiocontrol = "disabled" + TNC.low_bandwidth_mode = lowbwmode + Station.mygrid = bytes("AA12aa", "utf-8") + TNC.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 - static.ARQ_SESSION_STATE = "connected" + ARQ.arq_session_state = "connected" mycallsign = helpers.callsign_to_bytes(mycall) mycallsign = helpers.bytes_to_callsign(mycallsign) - static.MYCALLSIGN = mycallsign - static.MYCALLSIGN_CRC = helpers.get_crc_24(static.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) - static.DXCALLSIGN = dxcallsign - static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN) + Station.dxcallsign = dxcallsign + Station.dxcallsign_crc = helpers.get_crc_24(Station.dxcallsign) # Create the TNC tnc = data_handler.DATA() @@ -136,13 +136,13 @@ def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): # the queue to an object for comparisons. while ( '"arq":"transmission","status":"received"' not in str(sock.SOCKET_QUEUE.queue) - or static.ARQ_STATE + 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(static.ARQ_STATE)) + 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 @@ -151,7 +151,7 @@ def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): log.warning("station2", TIMEOUT=True) break time.sleep(0.5) - log.info("station2", arq_state=pformat(static.ARQ_STATE)) + log.info("station2", arq_state=pformat(ARQ.arq_state)) # log.info("S2 DQT: ", DQ_Tx=pformat(tnc.data_queue_transmit.queue)) # log.info("S2 DQR: ", DQ_Rx=pformat(tnc.data_queue_received.queue)) diff --git a/test/util_datac0.py b/test/util_datac13.py similarity index 78% rename from test/util_datac0.py rename to test/util_datac13.py index 13bd7b1b5..9efe2828b 100644 --- a/test/util_datac0.py +++ b/test/util_datac13.py @@ -5,9 +5,9 @@ Near end-to-end test for sending / receiving 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. +by the daemon process into and out of the TNCParam. -Invoked from test_datac0.py. +Invoked from test_datac13.py. @author: N2KIQ """ @@ -21,9 +21,10 @@ import helpers import modem import sock -import static -import structlog +from static import ARQ, HamlibParam, ModemParam, Station, TNC as TNCParam from static import FRAME_TYPE as FR_TYPE +import structlog +#from static import FRAME_TYPE as FR_TYPE def t_setup( @@ -46,33 +47,35 @@ def t_setup( modem.RXCHANNEL = tmp_path / rx_channel modem.TESTMODE = True modem.TXCHANNEL = tmp_path / tx_channel - static.HAMLIB_RADIOCONTROL = "disabled" - static.LOW_BANDWIDTH_MODE = lowbwmode or True - static.MYGRID = bytes("AA12aa", "utf-8") - static.RESPOND_TO_CQ = True - static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + HamlibParam.hamlib_radiocontrol = "disabled" + TNCParam.low_bandwidth_mode = lowbwmode or True + Station.mygrid = bytes("AA12aa", "utf-8") + TNCParam.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) - static.MYCALLSIGN = mycallsign - static.MYCALLSIGN_CRC = helpers.get_crc_24(static.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) - static.DXCALLSIGN = dxcallsign - static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN) + Station.dxcallsign = dxcallsign + Station.dxcallsign_crc = helpers.get_crc_24(Station.dxcallsign) # Create the TNC - tnc = data_handler.DATA() + tnc_data_handler = data_handler.DATA() orig_rx_func = data_handler.DATA.process_data data_handler.DATA.process_data = t_process_data - tnc.log = structlog.get_logger(f"station{station}_DATA") + tnc_data_handler.log = structlog.get_logger(f"station{station}_DATA") # Limit the frame-ack timeout - tnc.time_list_low_bw = [3, 1, 1] - tnc.time_list_high_bw = [3, 1, 1] - tnc.time_list = [3, 1, 1] + tnc_data_handler.time_list_low_bw = [8, 8, 8] + tnc_data_handler.time_list_high_bw = [8, 8, 8] + tnc_data_handler.time_list = [8, 8, 8] # Limit number of retries - tnc.rx_n_max_retries_per_burst = 4 + tnc_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() @@ -80,10 +83,10 @@ def t_setup( modem.RF.transmit = t_transmit t_modem.log = structlog.get_logger(f"station{station}_RF") - return tnc, orig_rx_func, orig_tx_func + return tnc_data_handler, orig_rx_func, orig_tx_func -def t_datac0_1( +def t_datac13_1( parent_pipe, mycall: str, dxcall: str, @@ -93,7 +96,7 @@ def t_datac0_1( log = structlog.get_logger("station1") orig_tx_func: Callable orig_rx_func: Callable - log.debug("t_datac0_1:", TMP_PATH=tmp_path) + log.debug("t_datac13_1:", TMP_PATH=tmp_path) # Unpack tuple data, timeout_duration, tx_check, _, final_tx_check, _ = config @@ -131,7 +134,7 @@ def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): # original function captured before this one was put in place. orig_rx_func(self, bytes_out, freedv, bytes_per_frame) # type: ignore - tnc, orig_rx_func, orig_tx_func = t_setup( + tnc_data_handler, orig_rx_func, orig_tx_func = t_setup( 1, mycall, dxcall, @@ -143,20 +146,20 @@ def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): tmp_path, ) - log.info("t_datac0_1:", RXCHANNEL=modem.RXCHANNEL) - log.info("t_datac0_1:", TXCHANNEL=modem.TXCHANNEL) + 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_datac0_1: STOP test, setting TNC state") - static.TNC_STATE = "BUSY" - static.ARQ_STATE = True + log.debug("t_datac13_1: STOP test, setting TNC state") + TNCParam.tnc_state = "BUSY" + ARQ.arq_state = True sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) time.sleep(0.5) sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) # Assure the test completes. - timeout = time.time() + timeout_duration + timeout = time.time() + timeout_duration while tx_check not in str(sock.SOCKET_QUEUE.queue): if time.time() > timeout: log.warning( @@ -166,25 +169,25 @@ def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): tx_check=tx_check, ) break - time.sleep(0.1) + time.sleep(0.5) log.info("station1, first") # override ARQ SESSION STATE for allowing disconnect command - static.ARQ_SESSION_STATE = "connected" + ARQ.arq_session_state = "connected" data = {"type": "arq", "command": "disconnect", "dxcallsign": dxcall} sock.ThreadedTCPRequestHandler.process_tnc_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 tnc.data_queue_transmit.queue: + timeout = time.time() + timeout_duration + while tnc_data_handler.data_queue_transmit.queue: if time.time() > timeout: - log.warning("station1", TIMEOUT=True, dq_tx=tnc.data_queue_transmit.queue) + log.warning("station1", TIMEOUT=True, dq_tx=tnc_data_handler.data_queue_transmit.queue) break time.sleep(0.5) log.info("station1, final") - # log.info("S1 DQT: ", DQ_Tx=pformat(tnc.data_queue_transmit.queue)) - # log.info("S1 DQR: ", DQ_Rx=pformat(tnc.data_queue_received.queue)) + # log.info("S1 DQT: ", DQ_Tx=pformat(TNCParam.data_queue_transmit.queue)) + # log.info("S1 DQR: ", DQ_Rx=pformat(TNCParam.data_queue_received.queue)) log.debug("S1 Socket: ", socket_queue=pformat(sock.SOCKET_QUEUE.queue)) for item in final_tx_check: @@ -199,7 +202,7 @@ def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): log.warning("station1: Exiting!") -def t_datac0_2( +def t_datac13_2( parent_pipe, mycall: str, dxcall: str, @@ -209,7 +212,7 @@ def t_datac0_2( log = structlog.get_logger("station2") orig_tx_func: Callable orig_rx_func: Callable - log.debug("t_datac0_2:", TMP_PATH=tmp_path) + log.debug("t_datac13_2:", TMP_PATH=tmp_path) # Unpack tuple data, timeout_duration, _, rx_check, _, final_rx_check = config @@ -259,8 +262,8 @@ def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): tmp_path, ) - log.info("t_datac0_2:", RXCHANNEL=modem.RXCHANNEL) - log.info("t_datac0_2:", TXCHANNEL=modem.TXCHANNEL) + log.info("t_datac13_2:", RXCHANNEL=modem.RXCHANNEL) + log.info("t_datac13_2:", TXCHANNEL=modem.TXCHANNEL) if "cq" in data: t_data = {"type": "arq", "command": "stop_transmission"} @@ -268,7 +271,7 @@ def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(t_data, indent=None)) # Assure the test completes. - timeout = time.time() + timeout_duration + 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): @@ -284,7 +287,7 @@ def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): log.info("station2, first") # Allow enough time for this side to receive the disconnect frame. - timeout = time.time() + timeout_duration + 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)) @@ -292,8 +295,8 @@ def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): time.sleep(0.5) log.info("station2, final") - # log.info("S2 DQT: ", DQ_Tx=pformat(tnc.data_queue_transmit.queue)) - # log.info("S2 DQR: ", DQ_Rx=pformat(tnc.data_queue_received.queue)) + # log.info("S2 DQT: ", DQ_Tx=pformat(TNCParam.data_queue_transmit.queue)) + # log.info("S2 DQR: ", DQ_Rx=pformat(TNCParam.data_queue_received.queue)) log.debug("S2 Socket: ", socket_queue=pformat(sock.SOCKET_QUEUE.queue)) for item in final_rx_check: diff --git a/test/util_datac0_negative.py b/test/util_datac13_negative.py similarity index 77% rename from test/util_datac0_negative.py rename to test/util_datac13_negative.py index 08c881e01..882acc985 100644 --- a/test/util_datac0_negative.py +++ b/test/util_datac13_negative.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -Negative test utilities for datac0 frames. +Negative test utilities for datac13 frames. @author: kronenpj """ @@ -15,9 +15,9 @@ import helpers import modem import sock -import static +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC, FRAME_TYPE as FR_TYPE import structlog -from static import FRAME_TYPE as FR_TYPE +#from static import FRAME_TYPE as FR_TYPE def t_setup( @@ -40,44 +40,44 @@ def t_setup( modem.RXCHANNEL = tmp_path / rx_channel modem.TESTMODE = True modem.TXCHANNEL = tmp_path / tx_channel - static.HAMLIB_RADIOCONTROL = "disabled" - static.LOW_BANDWIDTH_MODE = lowbwmode or True - static.MYGRID = bytes("AA12aa", "utf-8") - static.RESPOND_TO_CQ = True - static.SSID_LIST = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - - mycallsign = helpers.callsign_to_bytes(mycall) - mycallsign = helpers.bytes_to_callsign(mycallsign) - static.MYCALLSIGN = mycallsign - static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN) - - dxcallsign = helpers.callsign_to_bytes(dxcall) - dxcallsign = helpers.bytes_to_callsign(dxcallsign) - static.DXCALLSIGN = dxcallsign - static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN) + HamlibParam.hamlib_radiocontrol = "disabled" + TNC.low_bandwidth_mode = lowbwmode or True + Station.mygrid = bytes("AA12aa", "utf-8") + TNC.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 TNC - tnc = data_handler.DATA() + tnc_data_handler = data_handler.DATA() orig_rx_func = data_handler.DATA.process_data data_handler.DATA.process_data = t_process_data - tnc.log = structlog.get_logger(f"station{station}_DATA") + tnc_data_handler.log = structlog.get_logger(f"station{station}_DATA") # Limit the frame-ack timeout - tnc.time_list_low_bw = [3, 1, 1] - tnc.time_list_high_bw = [3, 1, 1] - tnc.time_list = [3, 1, 1] + tnc_data_handler.time_list_low_bw = [8, 8, 8] + tnc_data_handler.time_list_high_bw = [8, 8, 8] + tnc_data_handler.time_list = [8, 8, 8] # Limit number of retries - tnc.rx_n_max_retries_per_burst = 4 - + tnc_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 tnc, orig_rx_func, orig_tx_func + return tnc_data_handler, orig_rx_func, orig_tx_func -def t_datac0_1( +def t_datac13_1( parent_pipe, mycall: str, dxcall: str, @@ -87,7 +87,7 @@ def t_datac0_1( log = structlog.get_logger("station1") orig_tx_func: Callable orig_rx_func: Callable - log.debug("t_datac0_1:", TMP_PATH=tmp_path) + log.debug("t_datac13_1:", TMP_PATH=tmp_path) # Unpack tuple data, timeout_duration, tx_check, _, final_tx_check, _ = config @@ -125,7 +125,7 @@ def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): # original function captured before this one was put in place. orig_rx_func(self, bytes_out, freedv, bytes_per_frame) # type: ignore - tnc, orig_rx_func, orig_tx_func = t_setup( + tnc_data_handler, orig_rx_func, orig_tx_func = t_setup( 1, mycall, dxcall, @@ -137,21 +137,21 @@ def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): tmp_path, ) - log.info("t_datac0_1:", RXCHANNEL=modem.RXCHANNEL) - log.info("t_datac0_1:", TXCHANNEL=modem.TXCHANNEL) + log.info("t_datac13_1:", RXCHANNEL=modem.RXCHANNEL) + log.info("t_datac13_1:", TXCHANNEL=modem.TXCHANNEL) - orig_dxcall = static.DXCALLSIGN + orig_dxcall = Station.dxcallsign if "stop" in data["command"]: time.sleep(0.5) log.debug( - "t_datac0_1: STOP test, setting TNC state", - mycall=static.MYCALLSIGN, - dxcall=static.DXCALLSIGN, + "t_datac13_1: STOP test, setting TNC state", + mycall=Station.mycallsign, + dxcall=Station.dxcallsign, ) - static.DXCALLSIGN = helpers.callsign_to_bytes(data["dxcallsign"]) - static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN) - static.TNC_STATE = "BUSY" - static.ARQ_STATE = True + Station.dxcallsign = helpers.callsign_to_bytes(data["dxcallsign"]) + Station.dxcallsign_CRC = helpers.get_crc_24(Station.dxcallsign) + TNC.tnc_state = "BUSY" + ARQ.arq_state = True sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) @@ -172,19 +172,19 @@ def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): if "stop" in data["command"]: time.sleep(0.5) log.debug("STOP test, resetting DX callsign") - static.DXCALLSIGN = orig_dxcall - static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN) + Station.dxcallsign = orig_dxcall + Station.dxcallsign_CRC = helpers.get_crc_24(Station.dxcallsign) # override ARQ SESSION STATE for allowing disconnect command - static.ARQ_SESSION_STATE = "connected" + ARQ.arq_session_state = "connected" data = {"type": "arq", "command": "disconnect", "dxcallsign": dxcall} sock.ThreadedTCPRequestHandler.process_tnc_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 tnc.data_queue_transmit.queue: + while tnc_data_handler.data_queue_transmit.queue: if time.time() > timeout: - log.warning("station1", TIMEOUT=True, dq_tx=tnc.data_queue_transmit.queue) + log.warning("station1", TIMEOUT=True, dq_tx=tnc_data_handler.data_queue_transmit.queue) break time.sleep(0.5) log.info("station1, final") @@ -204,7 +204,7 @@ def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): log.warning("station1: Exiting!") -def t_datac0_2( +def t_datac13_2( parent_pipe, mycall: str, dxcall: str, @@ -214,7 +214,7 @@ def t_datac0_2( log = structlog.get_logger("station2") orig_tx_func: Callable orig_rx_func: Callable - log.debug("t_datac0_2:", TMP_PATH=tmp_path) + log.debug("t_datac13_2:", TMP_PATH=tmp_path) # Unpack tuple data, timeout_duration, _, rx_check, _, final_rx_check = config @@ -264,9 +264,9 @@ def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): tmp_path, ) - log.info("t_datac0_2:", RXCHANNEL=modem.RXCHANNEL) - log.info("t_datac0_2:", TXCHANNEL=modem.TXCHANNEL) - log.info("t_datac0_2:", mycall=static.MYCALLSIGN) + 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"} @@ -292,9 +292,6 @@ def t_process_data(self, bytes_out, freedv, bytes_per_frame: int): # 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 diff --git a/test/util_multimode_rx.py b/test/util_multimode_rx.py index 61af5e567..5e59431f4 100755 --- a/test/util_multimode_rx.py +++ b/test/util_multimode_rx.py @@ -67,7 +67,7 @@ def test_mm_rx(): for idx in range(3): datac_freedv.append( ctypes.cast( - codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), ctypes.c_void_p + codec2.api.freedv_open(codec2.FREEDV_MODE.datac13.value), ctypes.c_void_p ) ) datac_bytes_per_frame.append( @@ -121,11 +121,11 @@ def test_mm_rx(): for idx in range(3): datac_nin[idx] = codec2.api.freedv_nin(datac_freedv[idx]) - def print_stats(time_datac0, time_datac1, time_datac3): + def print_stats(time_datac13, time_datac1, time_datac3): if not DEBUGGING_MODE: return - time_datac = [time_datac0, time_datac1, time_datac3] + 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[ @@ -206,7 +206,7 @@ def print_stats(time_datac0, time_datac1, time_datac3): print("TIMEOUT REACHED", file=sys.stderr) print( - f"DATAC0: {rx_bursts_datac[0]}/{rx_total_frames_datac[0]} " + 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, @@ -241,7 +241,7 @@ def parse_arguments(): parser.add_argument( "--timeout", dest="TIMEOUT", - default=10, + default=60, type=int, help="Timeout (seconds) before test ends", ) diff --git a/test/util_multimode_tx.py b/test/util_multimode_tx.py index c53d37c6c..00d99b66b 100644 --- a/test/util_multimode_tx.py +++ b/test/util_multimode_tx.py @@ -49,9 +49,9 @@ def test_mm_tx(): data_out = b"HELLO WORLD!" modes = [ - codec2.api.FREEDV_MODE_DATAC0, - codec2.api.FREEDV_MODE_DATAC1, - codec2.api.FREEDV_MODE_DATAC3, + codec2.FREEDV_MODE.datac13.value, + codec2.FREEDV_MODE.datac1.value, + codec2.FREEDV_MODE.datac3.value, ] if AUDIO_OUTPUT_DEVICE != -1: diff --git a/test/util_rx.py b/test/util_rx.py index ccbaaf89c..620c256ae 100644 --- a/test/util_rx.py +++ b/test/util_rx.py @@ -221,7 +221,7 @@ def parse_arguments(): "--framesperburst", dest="N_FRAMES_PER_BURST", default=1, type=int ) parser.add_argument( - "--mode", dest="FREEDV_MODE", type=str, choices=["datac0", "datac1", "datac3"] + "--mode", dest="FREEDV_MODE", type=str, choices=["datac13", "datac1", "datac3"] ) parser.add_argument( "--audiodev", @@ -234,7 +234,7 @@ def parse_arguments(): parser.add_argument( "--timeout", dest="TIMEOUT", - default=10, + default=60, type=int, help="Timeout (seconds) before test ends", ) diff --git a/test/util_tnc_ISS.py b/test/util_tnc_ISS.py index 4c9172d11..f1a69f19c 100644 --- a/test/util_tnc_ISS.py +++ b/test/util_tnc_ISS.py @@ -122,18 +122,18 @@ def t_arq_iss(*args): else: assert not MESSAGE, f"{MESSAGE} not known to test." - time.sleep(0.5) + time.sleep(2.5) sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) - time.sleep(1.5) + time.sleep(7.5) data = {"type": "arq", "command": "stop_transmission"} sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) - time.sleep(0.5) + time.sleep(2.5) sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None)) # Set timeout diff --git a/test/util_tx.py b/test/util_tx.py index 06e6a6360..c4cc6ec1d 100644 --- a/test/util_tx.py +++ b/test/util_tx.py @@ -198,7 +198,7 @@ def parse_arguments(): help="delay between bursts in ms", ) parser.add_argument( - "--mode", dest="FREEDV_MODE", type=str, choices=["datac0", "datac1", "datac3"] + "--mode", dest="FREEDV_MODE", type=str, choices=["datac13", "datac1", "datac3"] ) parser.add_argument( "--audiodev", diff --git a/tnc/codec2.py b/tnc/codec2.py index f578cac55..424ac8d3b 100644 --- a/tnc/codec2.py +++ b/tnc/codec2.py @@ -18,20 +18,38 @@ log = structlog.get_logger("codec2") + # Enum for codec2 modes class FREEDV_MODE(Enum): """ Enumeration for codec2 modes and names """ - sig0 = 14 - sig1 = 14 + sig0 = 19 + sig1 = 19 datac0 = 14 datac1 = 10 datac3 = 12 + datac4 = 18 + datac13 = 19 fsk_ldpc = 9 fsk_ldpc_0 = 200 fsk_ldpc_1 = 201 +class FREEDV_MODE_USED_SLOTS(Enum): + """ + Enumeration for codec2 used slots + """ + sig0 = [False, False, True, False, False] + sig1 = [False, False, True, False, False] + datac0 = [False, False, True, False, False] + datac1 = [False, True, True, True, False] + datac3 = [False, False, True, False, False] + datac4 = [False, False, True, False, False] + datac13 = [False, False, True, False, False] + fsk_ldpc = [False, False, True, False, False] + fsk_ldpc_0 = [False, False, True, False, False] + fsk_ldpc_1 = [False, False, True, False, False] + # Function for returning the mode value def freedv_get_mode_value_by_name(mode: str) -> int: """ @@ -101,6 +119,9 @@ def freedv_get_mode_name_by_value(mode: int) -> str: api.freedv_open.argype = [ctypes.c_int] # type: ignore api.freedv_open.restype = ctypes.c_void_p +api.freedv_set_sync.argype = [ctypes.c_void_p, ctypes.c_int] # type: ignore +api.freedv_set_sync.restype = ctypes.c_void_p + api.freedv_open_advanced.argtype = [ctypes.c_int, ctypes.c_void_p] # type: ignore api.freedv_open_advanced.restype = ctypes.c_void_p @@ -150,10 +171,6 @@ def freedv_get_mode_name_by_value(mode: int) -> str: api.freedv_get_n_max_modem_samples.restype = ctypes.c_int api.FREEDV_FS_8000 = 8000 # type: ignore -api.FREEDV_MODE_DATAC1 = 10 # type: ignore -api.FREEDV_MODE_DATAC3 = 12 # type: ignore -api.FREEDV_MODE_DATAC0 = 14 # type: ignore -api.FREEDV_MODE_FSK_LDPC = 9 # type: ignore # -------------------------------- FSK LDPC MODE SETTINGS @@ -216,7 +233,7 @@ class ADVANCED(ctypes.Structure): # ------- MODEM STATS STRUCTURES MODEM_STATS_NC_MAX = 50 + 1 * 2 -MODEM_STATS_NR_MAX = 160 * 2 +MODEM_STATS_NR_MAX = 320 * 2 MODEM_STATS_ET_MAX = 8 MODEM_STATS_EYE_IND_MAX = 160 MODEM_STATS_NSPEC = 512 diff --git a/tnc/config.ini b/tnc/config.ini index 0f15a8df2..d9c94013a 100644 --- a/tnc/config.ini +++ b/tnc/config.ini @@ -4,20 +4,20 @@ tncport = 3000 [STATION] #station settings -mycall = DJ2LS-9 -mygrid = JN12AA +mycall = DN2LS-0 +mygrid = JN48cs ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [AUDIO] #audio settings rx = 0 -tx = 1 -txaudiolevel = 78 +tx = 0 +txaudiolevel = 250 auto_tune = False [RADIO] #radio settings -radiocontrol = rigctld +radiocontrol = disabled rigctld_ip = 127.0.0.1 rigctld_port = 4532 @@ -26,12 +26,12 @@ rigctld_port = 4532 scatter = True fft = True narrowband = False -fmin = -250.0 -fmax = 250.0 +fmin = -150.0 +fmax = 150.0 qrv = True rxbuffersize = 16 explorer = False stats = False fsk = False -tx_delay = 0 +tx_delay = 800 diff --git a/tnc/daemon.py b/tnc/daemon.py index 71e70b844..bccca86e4 100755 --- a/tnc/daemon.py +++ b/tnc/daemon.py @@ -26,7 +26,8 @@ import log_handler import serial.tools.list_ports import sock -import static +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC + import structlog import ujson as json import config @@ -82,10 +83,10 @@ def update_audio_devices(self): """ while True: try: - if not static.TNCSTARTED: + if not Daemon.tncstarted: ( - static.AUDIO_INPUT_DEVICES, - static.AUDIO_OUTPUT_DEVICES, + AudioParam.audio_input_devices, + AudioParam.audio_output_devices, ) = audio.get_audio_devices() except Exception as err1: self.log.error( @@ -112,7 +113,7 @@ def update_serial_devices(self): {"port": str(port), "description": str(description)} ) - static.SERIAL_DEVICES = serial_devices + Daemon.serial_devices = serial_devices threading.Event().wait(1) except Exception as err1: self.log.error( @@ -210,10 +211,10 @@ def start_tnc(self, data): self.log.warning("[DMN] Starting TNC", rig=data[5], port=data[6]) # list of parameters, necessary for running subprocess command as a list - options = ["--port", str(static.DAEMONPORT - 1)] + options = ["--port", str(DAEMON.port - 1)] # create an additional list entry for parameters not covered by gui - data[50] = int(static.DAEMONPORT - 1) + data[50] = int(DAEMON.port - 1) options.append("--mycall") options.extend((data[1], "--mygrid")) @@ -317,8 +318,8 @@ def start_tnc(self, data): self.log.info("[DMN] TNC started", path="source") - static.TNCPROCESS = proc - static.TNCSTARTED = True + Daemon.tncprocess = proc + Daemon.tncstarted = True if __name__ == "__main__": mainlog = structlog.get_logger(__file__) # we need to run this on Windows for multiprocessing support @@ -335,7 +336,7 @@ def start_tnc(self, data): ) ARGS = PARSER.parse_args() - static.DAEMONPORT = ARGS.socket_port + DAEMON.port = ARGS.socket_port try: if sys.platform == "linux": @@ -363,11 +364,11 @@ def start_tnc(self, data): config = config.CONFIG("config.ini") try: - mainlog.info("[DMN] Starting TCP/IP socket", port=static.DAEMONPORT) + mainlog.info("[DMN] Starting TCP/IP socket", port=DAEMON.port) # https://stackoverflow.com/a/16641793 socketserver.TCPServer.allow_reuse_address = True cmdserver = sock.ThreadedTCPServer( - (static.HOST, static.DAEMONPORT), sock.ThreadedTCPRequestHandler + (TNC.host, DAEMON.port), sock.ThreadedTCPRequestHandler ) server_thread = threading.Thread(target=cmdserver.serve_forever) server_thread.daemon = True @@ -375,7 +376,7 @@ def start_tnc(self, data): except Exception as err: mainlog.error( - "[DMN] Starting TCP/IP socket failed", port=static.DAEMONPORT, e=err + "[DMN] Starting TCP/IP socket failed", port=DAEMON.port, e=err ) sys.exit(1) daemon = DAEMON() @@ -384,7 +385,7 @@ def start_tnc(self, data): "[DMN] Starting FreeDATA Daemon", author="DJ2LS", year="2023", - version=static.VERSION, + version=TNC.version, ) while True: threading.Event().wait(1) diff --git a/tnc/data_handler.py b/tnc/data_handler.py index 704119783..4c1f337ae 100644 --- a/tnc/data_handler.py +++ b/tnc/data_handler.py @@ -21,15 +21,16 @@ import modem import numpy as np import sock -import static +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC import structlog import stats import ujson as json -from codec2 import FREEDV_MODE +from codec2 import FREEDV_MODE, FREEDV_MODE_USED_SLOTS from exceptions import NoCallsign from queues import DATA_QUEUE_RECEIVED, DATA_QUEUE_TRANSMIT, RX_BUFFER from static import FRAME_TYPE as FR_TYPE + TESTMODE = False @@ -38,14 +39,13 @@ class DATA: log = structlog.get_logger("DATA") - def __init__(self) -> None: self.stats = stats.stats() # Initial call sign. Will be overwritten later - self.mycallsign = static.MYCALLSIGN - self.dxcallsign = static.DXCALLSIGN + self.mycallsign = Station.mycallsign + self.dxcallsign = Station.dxcallsign self.data_queue_transmit = DATA_QUEUE_TRANSMIT self.data_queue_received = DATA_QUEUE_RECEIVED @@ -54,6 +54,11 @@ def __init__(self) -> None: self.length_sig0_frame = 14 self.length_sig1_frame = 14 + # duration of signalling frame + self.duration_sig0_frame = 2.3 + self.duration_sig1_frame = 2.3 + self.longest_duration = 5.8 # datac5 + # hold session id self.session_id = bytes(1) @@ -70,17 +75,19 @@ def __init__(self) -> None: self.transmission_uuid = "" - self.burst_last_received = 0.0 # time of last "live sign" of a burst + 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 self.burst_ack_snr = 0 # SNR from received burst ack frames - # Flag to indicate if we received an acknowledge frame for a burst + # Flag to indicate if we received an ACK frame for a burst self.burst_ack = False - # Flag to indicate if we received an acknowledge frame for a data frame + # 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 an request for repeater frames + # 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 + self.rx_start_of_transmission = 0 # time of transmission start # 3 bytes for the BOF Beginning of File indicator in a data frame @@ -91,8 +98,11 @@ def __init__(self) -> None: self.tx_n_max_retries_per_burst = 40 self.rx_n_max_retries_per_burst = 40 self.n_retries_per_burst = 0 + self.rx_n_frame_of_burst = 0 + self.rx_n_frames_per_burst = 0 + self.max_n_frames_per_burst = 1 - # Flag to indicate if we recevied a low bandwidth mode channel opener + # Flag to indicate if we received a low bandwidth mode channel opener self.received_LOW_BANDWIDTH_MODE = False self.data_channel_max_retries = 15 @@ -105,31 +115,33 @@ def __init__(self) -> None: # List of codec2 modes to use in "low bandwidth" mode. self.mode_list_low_bw = [ - FREEDV_MODE.datac3.value, + FREEDV_MODE.datac4.value, ] # List for minimum SNR operating level for the corresponding mode in self.mode_list - self.snr_list_low_bw = [0] + self.snr_list_low_bw = [-100] # List for time to wait for corresponding mode in seconds - self.time_list_low_bw = [6] + self.time_list_low_bw = [6 + self.duration_sig0_frame + 1] # --------------------- 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 = [0, 3] + 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 - self.time_list_high_bw = [7, 8] + # TODO: Adjust these times + self.time_list_high_bw = [6 + self.duration_sig0_frame + 1, 6 + self.duration_sig0_frame + 1, 6 + self.duration_sig0_frame + 1] # -------------- 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 static.LOW_BANDWIDTH_MODE: + if TNC.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 @@ -142,13 +154,15 @@ def __init__(self) -> None: self.time_list = self.time_list_high_bw self.speed_level = len(self.mode_list) - 1 # speed level for selecting mode - static.ARQ_SPEED_LEVEL = self.speed_level + ARQ.arq_speed_level = self.speed_level # 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 = 126 - self.arq_burst_header_size + 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 self.is_IRS = False self.burst_nack = False @@ -160,9 +174,9 @@ def __init__(self) -> None: self.rx_frame_eof_received = False # TIMEOUTS - self.burst_ack_timeout_seconds = 3.0 # timeout for burst acknowledges - self.data_frame_ack_timeout_seconds = 3.0 # timeout for data frame acknowledges - self.rpt_ack_timeout_seconds = 3.0 # timeout for rpt frame acknowledges + self.burst_ack_timeout_seconds = 4.5 # timeout for burst acknowledges + self.data_frame_ack_timeout_seconds = 4.5 # timeout for data frame acknowledges + self.rpt_ack_timeout_seconds = 4.5 # timeout for rpt frame acknowledges self.transmission_timeout = 180 # transmission timeout in seconds # Dictionary of functions and log messages used in process_data @@ -211,7 +225,7 @@ def __init__(self) -> None: } self.command_dispatcher = { - #"CONNECT": (self.arq_session_handler, "CONNECT"), + # "CONNECT": (self.arq_session_handler, "CONNECT"), "CQ": (self.transmit_cq, "CQ"), "DISCONNECT": (self.close_session, "DISCONNECT"), "SEND_TEST_FRAME": (self.send_test_frame, "TEST"), @@ -257,10 +271,10 @@ def worker_transmit(self) -> None: # stuck in IRS # # send transmission queued information once - if static.ARQ_STATE or static.IS_CODEC2_TRAFFIC: + if ARQ.arq_state or ModemParam.is_codec2_traffic: self.log.debug( "[TNC] TX DISPATCHER - waiting with processing command ", - arq_state=static.ARQ_STATE, + arq_state=ARQ.arq_state, ) self.send_data_to_socket_queue( @@ -270,7 +284,7 @@ def worker_transmit(self) -> None: ) # now stay in while loop until state released - while static.ARQ_STATE or static.IS_CODEC2_TRAFFIC: + while ARQ.arq_state or ModemParam.is_codec2_traffic: threading.Event().wait(0.01) # and finally sleep some time @@ -298,9 +312,9 @@ def worker_transmit(self) -> None: # [2] STATE bool if data[2]: self.beacon_interval = data[1] - static.BEACON_STATE = True + Beacon.beacon_state = True else: - static.BEACON_STATE = False + Beacon.beacon_state = False elif data[0] == "ARQ_RAW": # [1] DATA_OUT bytes @@ -375,12 +389,12 @@ def process_data(self, bytes_out, freedv, bytes_per_frame: int) -> None: or _valid4 or frametype in [ - FR_TYPE.CQ.value, - FR_TYPE.QRV.value, - FR_TYPE.PING.value, - FR_TYPE.BEACON.value, - FR_TYPE.IS_WRITING.value, - ] + FR_TYPE.CQ.value, + FR_TYPE.QRV.value, + FR_TYPE.PING.value, + FR_TYPE.BEACON.value, + FR_TYPE.IS_WRITING.value, + ] ): # CHECK IF FRAMETYPE IS BETWEEN 10 and 50 ------------------------ @@ -398,7 +412,7 @@ def process_data(self, bytes_out, freedv, bytes_per_frame: int) -> None: # get snr of received data # FIXME: find a fix for this - after moving to classes, this no longer works # snr = self.calculate_snr(freedv) - snr = static.SNR + snr = ModemParam.snr self.log.debug("[TNC] RX SNR", snr=snr) # send payload data to arq checker without CRC16 self.arq_data_received( @@ -406,7 +420,7 @@ def process_data(self, bytes_out, freedv, bytes_per_frame: int) -> None: ) # if we received the last frame of a burst or the last remaining rpt frame, do a modem unsync - # if static.RX_BURST_BUFFER.count(None) <= 1 or (frame+1) == n_frames_per_burst: + # if ARQ.rx_burst_buffer.count(None) <= 1 or (frame+1) == n_frames_per_burst: # self.log.debug(f"[TNC] LAST FRAME OF BURST --> UNSYNC {frame+1}/{n_frames_per_burst}") # self.c_lib.freedv_set_sync(freedv, 0) @@ -430,8 +444,8 @@ def process_data(self, bytes_out, freedv, bytes_per_frame: int) -> None: def enqueue_frame_for_tx( self, - frame_to_tx,# : list[bytearray], # this causes a crash on python 3.7 - c2_mode=FREEDV_MODE.datac0.value, + frame_to_tx, # : list[bytearray], # this causes a crash on python 3.7 + c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0, ) -> None: @@ -440,23 +454,26 @@ def enqueue_frame_for_tx( :param frame_to_tx: Frame data to send :type frame_to_tx: list of bytearrays - :param c2_mode: Codec2 mode to use, defaults to 14 (datac0) + :param c2_mode: Codec2 mode to use, defaults to datac13 :type c2_mode: int, optional :param copies: Number of frame copies to send, defaults to 1 :type copies: int, optional :param repeat_delay: Delay time before sending repeat frame, defaults to 0 :type repeat_delay: int, optional """ + print(frame_to_tx[0]) + print(frame_to_tx) frame_type = FR_TYPE(int.from_bytes(frame_to_tx[0][:1], byteorder="big")).name - self.log.debug("[TNC] enqueue_frame_for_tx", c2_mode=FREEDV_MODE(c2_mode).name, data=frame_to_tx, type=frame_type) + self.log.debug("[TNC] enqueue_frame_for_tx", c2_mode=FREEDV_MODE(c2_mode).name, data=frame_to_tx, + type=frame_type) # Set the TRANSMITTING flag before adding an object to the transmit queue # TODO: This is not that nice, we could improve this somehow - static.TRANSMITTING = True + TNC.transmitting = True modem.MODEM_TRANSMIT_QUEUE.put([c2_mode, copies, repeat_delay, frame_to_tx]) # Wait while transmitting - while static.TRANSMITTING: + while TNC.transmitting: threading.Event().wait(0.01) def send_data_to_socket_queue(self, **jsondata): @@ -473,8 +490,8 @@ def send_data_to_socket_queue(self, **jsondata): uuid=self.transmission_uuid, timestamp=timestamp, mycallsign=str(self.mycallsign, "UTF-8"), - dxcallsign=str(static.DXCALLSIGN, "UTF-8"), - dxgrid=str(static.DXGRID, "UTF-8"), + dxcallsign=str(Station.dxcallsign, "UTF-8"), + dxgrid=str(Station.dxgrid, "UTF-8"), data=base64_data, ) """ @@ -485,7 +502,7 @@ def send_data_to_socket_queue(self, **jsondata): if "mycallsign" not in jsondata: jsondata["mycallsign"] = str(self.mycallsign, "UTF-8") if "dxcallsign" not in jsondata: - jsondata["dxcallsign"] = str(static.DXCALLSIGN, "UTF-8") + jsondata["dxcallsign"] = str(Station.dxcallsign, "UTF-8") except Exception as e: self.log.debug("[TNC] error adding callsigns to network message", e=e) @@ -504,7 +521,7 @@ def send_ident_frame(self, transmit) -> None: # Transmit frame if transmit: - self.enqueue_frame_for_tx([ident_frame], c2_mode=FREEDV_MODE.datac0.value) + self.enqueue_frame_for_tx([ident_frame], c2_mode=FREEDV_MODE.sig0.value) else: return ident_frame @@ -516,19 +533,16 @@ def send_burst_ack_frame(self, snr) -> None: 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(static.RX_FRAME_BUFFER).to_bytes(4, byteorder="big") + ack_frame[4:8] = len(ARQ.rx_frame_buffer).to_bytes(4, byteorder="big") # wait while timeout not reached and our busy state is busy channel_busy_timeout = time.time() + 5 - while static.CHANNEL_BUSY and time.time() < channel_busy_timeout: + while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): threading.Event().wait(0.01) # Transmit frame self.enqueue_frame_for_tx([ack_frame], c2_mode=FREEDV_MODE.sig1.value) - # reset burst timeout in case we had to wait too long - self.burst_last_received = time.time() - def send_data_ack_frame(self, snr) -> None: """Build and send ACK frame for received DATA frame""" @@ -539,37 +553,30 @@ def send_data_ack_frame(self, snr) -> None: # wait while timeout not reached and our busy state is busy channel_busy_timeout = time.time() + 5 - while static.CHANNEL_BUSY and time.time() < channel_busy_timeout: + while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): threading.Event().wait(0.01) + # reset burst timeout in case we had to wait too long + self.burst_last_received = time.time() + channel_busy_timeout + 8 # Transmit frame # TODO: Do we have to send , self.send_ident_frame(False) ? # self.enqueue_frame_for_tx([ack_frame, self.send_ident_frame(False)], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) - self.enqueue_frame_for_tx([ack_frame], c2_mode=FREEDV_MODE.sig1.value, copies=6, repeat_delay=0) + self.enqueue_frame_for_tx([ack_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_retransmit_request_frame(self, freedv) -> None: + 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. + print(ARQ.rx_burst_buffer) missing_frames = [ frame + 1 - for frame, element in enumerate(static.RX_BURST_BUFFER) + for frame, element in enumerate(ARQ.rx_burst_buffer) if element is None ] - # set n frames per burst to modem - # this is an idea, so it's not getting lost.... - # we need to work on this - codec2.api.freedv_set_frames_per_burst(freedv, len(missing_frames)) - - # TODO: Trim `missing_frames` bytesarray to [7:13] (6) frames, if it's larger. - # TODO: Instead of using int we could use a binary flag - # then create a repeat frame 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("[TNC] ARQ | RX | Requesting", frames=missing_frames) # Transmit frame @@ -583,9 +590,7 @@ def send_burst_nack_frame(self, snr: bytes) -> None: 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(static.RX_FRAME_BUFFER).to_bytes(4, byteorder="big") - - + nack_frame[4:8] = len(ARQ.rx_frame_buffer).to_bytes(4, byteorder="big") # TRANSMIT NACK FRAME FOR BURST # TODO: Do we have to send ident frame? @@ -593,19 +598,23 @@ def send_burst_nack_frame(self, snr: bytes) -> None: # wait while timeout not reached and our busy state is busy channel_busy_timeout = time.time() + 5 - while static.CHANNEL_BUSY and time.time() < channel_busy_timeout: + while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): threading.Event().wait(0.01) - self.enqueue_frame_for_tx([nack_frame], c2_mode=FREEDV_MODE.sig1.value, copies=6, repeat_delay=0) + 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, snr: bytes) -> None: + def send_burst_nack_frame_watchdog(self, snr: bytes, 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 + ARQ.rx_burst_buffer = [] + # Create and send ACK frame self.log.info("[TNC] ARQ | RX | SENDING NACK") nack_frame = bytearray(self.length_sig1_frame) @@ -613,16 +622,17 @@ def send_burst_nack_frame_watchdog(self, snr: bytes) -> None: 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:5] = bytes([int(tx_n_frames_per_burst)]) + nack_frame[5:9] = len(ARQ.rx_frame_buffer).to_bytes(4, byteorder="big") + # wait while timeout not reached and our busy state is busy - channel_busy_timeout = time.time() + 5 - while static.CHANNEL_BUSY and time.time() < channel_busy_timeout: + channel_busy_timeout = time.time() + 5 + 5 + while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): threading.Event().wait(0.01) # TRANSMIT NACK FRAME FOR BURST self.enqueue_frame_for_tx([nack_frame], c2_mode=FREEDV_MODE.sig1.value, copies=1, repeat_delay=0) - # reset burst timeout in case we had to wait too long - self.burst_last_received = time.time() # reset frame counter for not increasing speed level self.frame_received_counter = 0 @@ -632,7 +642,7 @@ def send_disconnect_frame(self) -> None: 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] = static.DXCALLSIGN_CRC + disconnection_frame[2:5] = Station.dxcallsign_crc # TODO: Needed? disconnection_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign) # self.enqueue_frame_for_tx([disconnection_frame, self.send_ident_frame(False)], c2_mode=FREEDV_MODE.sig0.value, copies=5, repeat_delay=0) # TODO: We need to add the ident frame feature with a seperate PR after publishing latest protocol @@ -641,10 +651,10 @@ def send_disconnect_frame(self) -> None: # wait while timeout not reached and our busy state is busy channel_busy_timeout = time.time() + 5 - while static.CHANNEL_BUSY and time.time() < channel_busy_timeout: + while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): threading.Event().wait(0.01) - self.enqueue_frame_for_tx([disconnection_frame], c2_mode=FREEDV_MODE.sig0.value, copies=6, repeat_delay=0) + self.enqueue_frame_for_tx([disconnection_frame], c2_mode=FREEDV_MODE.sig0.value, copies=3, repeat_delay=0) def arq_data_received( self, data_in: bytes, bytes_per_frame: int, snr: float, freedv @@ -663,14 +673,15 @@ def arq_data_received( data_in = bytes(data_in) # only process data if we are in ARQ and BUSY state else return to quit - if not static.ARQ_STATE and static.TNC_STATE not in ["BUSY"]: - self.log.warning("[TNC] wrong tnc state - dropping data", arq_state=static.ARQ_STATE, tnc_state=static.TNC_STATE) + if not ARQ.arq_state and TNC.tnc_state not in ["BUSY"]: + self.log.warning("[TNC] wrong tnc state - dropping data", arq_state=ARQ.arq_state, + tnc_state=TNC.tnc_state) return self.arq_file_transfer = True - static.TNC_STATE = "BUSY" - static.ARQ_STATE = True + TNC.tnc_state = "BUSY" + ARQ.arq_state = True # Update data_channel timestamp self.data_channel_last_received = int(time.time()) @@ -678,43 +689,42 @@ def arq_data_received( # Extract some important data from the frame # Get sequence number of burst frame - rx_n_frame_of_burst = int.from_bytes(bytes(data_in[:1]), "big") - 10 + self.rx_n_frame_of_burst = int.from_bytes(bytes(data_in[:1]), "big") - 10 # Get number of bursts from received frame - rx_n_frames_per_burst = int.from_bytes(bytes(data_in[1:2]), "big") + 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(static.RX_BURST_BUFFER) != rx_n_frames_per_burst: - static.RX_BURST_BUFFER = [None] * rx_n_frames_per_burst + if len(ARQ.rx_burst_buffer) != self.rx_n_frames_per_burst: + ARQ.rx_burst_buffer = [None] * self.rx_n_frames_per_burst # Append data to rx burst buffer - # [frame_type][n_frames_per_burst][CRC24][CRC24] - # static.RX_BURST_BUFFER[rx_n_frame_of_burst] = data_in[8:] # type: ignore - static.RX_BURST_BUFFER[rx_n_frame_of_burst] = data_in[3:] # type: ignore - - self.log.debug("[TNC] static.RX_BURST_BUFFER", buffer=static.RX_BURST_BUFFER) + ARQ.rx_burst_buffer[self.rx_n_frame_of_burst] = data_in[self.arq_burst_header_size:] # type: ignore - static.DXGRID = b'------' + Station.dxgrid = b'------' helpers.add_to_heard_stations( - static.DXCALLSIGN, - static.DXGRID, + Station.dxcallsign, + Station.dxgrid, "DATA-CHANNEL", snr, - static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY, + ModemParam.frequency_offset, + HamlibParam.hamlib_frequency, ) # Check if we received all frames in the burst by checking if burst buffer has no more "Nones" # This is the ideal case because we received all data - if None not in static.RX_BURST_BUFFER: + if None not in 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 static.RX_BURST_BUFFER: - # static.RX_FRAME_BUFFER += static.RX_BURST_BUFFER[i] + for value in ARQ.rx_burst_buffer: + # ARQ.rx_frame_buffer += ARQ.rx_burst_buffer[i] temp_burst_buffer += bytes(value) # type: ignore + # free up burst buffer + 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 @@ -723,52 +733,54 @@ def arq_data_received( # 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 static.RX_FRAME_BUFFER.endswith(temp_burst_buffer[:246]) and len(temp_burst_buffer) >= 246: + if ARQ.rx_frame_buffer.endswith(temp_burst_buffer[:246]) and len(temp_burst_buffer) >= 246: self.log.warning( "[TNC] ARQ | RX | wrong byteorder received - dropping data" ) # we need to run a return here, so we are not sending an ACK - return + # return except Exception as e: self.log.warning( "[TNC] ARQ | RX | wrong byteorder check failed", e=e ) + self.log.debug("[TNC] temp_burst_buffer", buffer=temp_burst_buffer) + self.log.debug("[TNC] ARQ.rx_frame_buffer", buffer=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 static.RX_FRAME_BUFFER.endswith(temp_burst_buffer): + if ARQ.rx_frame_buffer.endswith(temp_burst_buffer): self.log.info( "[TNC] ARQ | RX | Frame already received - sending ACK again" ) - static.RX_BURST_BUFFER = [] 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 - # static.RX_FRAME_BUFFER --> existing data + # ARQ.rx_frame_buffer --> existing data # temp_burst_buffer --> new data # search_area --> area where we want to search - # data_mode = self.mode_list[self.speed_level] - # payload_per_frame = modem.get_bytes_per_frame(data_mode) - 2 - # search_area = payload_per_frame - 3 # (3 bytes arq frame header) - search_area = self.arq_burst_maximum_payload # (3 bytes arq frame header) + search_area = self.arq_burst_last_payload * self.rx_n_frames_per_burst + + search_position = len(ARQ.rx_frame_buffer) - search_area + # if search position < 0, then search position = 0 + search_position = max(0, search_position) - search_position = len(static.RX_FRAME_BUFFER) - search_area # find position of data. returns -1 if nothing found in area else >= 0 # we are beginning from the end, so if data exists twice or more, # only the last one should be replaced # 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 = static.RX_FRAME_BUFFER[search_position:].rfind( + get_position = 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: - static.RX_FRAME_BUFFER = static.RX_FRAME_BUFFER[ + ARQ.rx_frame_buffer = ARQ.rx_frame_buffer[ : search_position + get_position ] self.log.warning( @@ -779,7 +791,10 @@ def arq_data_received( else: self.log.debug("[TNC] ARQ | RX | appending data to buffer") - static.RX_FRAME_BUFFER += temp_burst_buffer + 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 ( @@ -787,12 +802,14 @@ def arq_data_received( and not self.rx_frame_eof_received and data_in.find(self.data_frame_eof) < 0 ): - self.arq_calculate_speed_level(snr) + self.data_channel_last_received = int(time.time()) + 6 + 6 + self.burst_last_received = int(time.time()) + 6 + 6 # Create and send ACK frame - self.log.info("[TNC] ARQ | RX | SENDING ACK", finished=static.ARQ_SECONDS_UNTIL_FINISH, - bytesperminute=static.ARQ_BYTES_PER_MINUTE) + self.log.info("[TNC] ARQ | RX | SENDING ACK", finished=ARQ.arq_seconds_until_finish, + bytesperminute=ARQ.bytes_per_minute) + self.send_burst_ack_frame(snr) # Reset n retries per burst counter @@ -800,7 +817,7 @@ def arq_data_received( # calculate statistics self.calculate_transfer_rate_rx( - self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER) + self.rx_start_of_transmission, len(ARQ.rx_frame_buffer) ) # send a network message with information @@ -809,43 +826,56 @@ def arq_data_received( arq="transmission", status="receiving", uuid=self.transmission_uuid, - percent=static.ARQ_TRANSMISSION_PERCENT, - bytesperminute=static.ARQ_BYTES_PER_MINUTE, - compression=static.ARQ_COMPRESSION_FACTOR, + percent=ARQ.arq_transmission_percent, + bytesperminute=ARQ.bytes_per_minute, + compression=ARQ.arq_compression_factor, mycallsign=str(self.mycallsign, 'UTF-8'), dxcallsign=str(self.dxcallsign, 'UTF-8'), - finished=static.ARQ_SECONDS_UNTIL_FINISH, + finished=ARQ.arq_seconds_until_finish, irs=helpers.bool_to_string(self.is_IRS) ) - elif rx_n_frame_of_burst == rx_n_frames_per_burst - 1: - # We have "Nones" in our rx buffer, - # Check if we received last frame of burst - this is an indicator for missed frames. - # With this way of doing this, we always MUST receive the last - # frame of a burst otherwise the entire burst is lost - # TODO: See if a timeout on the send side with re-transmit last burst would help. - self.log.debug( - "[TNC] all frames in burst received:", - frame=rx_n_frame_of_burst, - frames=rx_n_frames_per_burst, - ) - self.send_retransmit_request_frame(freedv) - self.calculate_transfer_rate_rx( - self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER) - ) - + # elif self.rx_n_frame_of_burst == self.rx_n_frames_per_burst: + # # We have "Nones" in our rx buffer, + # # Check if we received last frame of burst - this is an indicator for missed frames. + # # With this way of doing this, we always MUST receive the last + # # frame of a burst otherwise the entire burst is lost + # # TODO: See if a timeout on the send side with re-transmit last burst would help. + # self.log.debug( + # "[TNC] last frames of burst received:", + # frame=self.rx_n_frame_of_burst, + # frames=self.rx_n_frames_per_burst, + # + # ) + # self.calculate_transfer_rate_rx( + # self.rx_start_of_transmission, len(ARQ.rx_frame_buffer) + # ) + + # elif self.rx_n_frame_of_burst not in [self.rx_n_frames_per_burst - 1]: + # self.log.info( + # "[TNC] data_handler: received burst", + # frame=self.rx_n_frame_of_burst + 1, + # frames=self.rx_n_frames_per_burst, + # ) + + # else: + # self.log.error( + # "[TNC] data_handler: Should not reach this point...", + # frame=self.rx_n_frame_of_burst + 1, + # frames=self.rx_n_frames_per_burst, + # ) else: - self.log.error( - "[TNC] data_handler: Should not reach this point...", - frame=rx_n_frame_of_burst, - frames=rx_n_frames_per_burst, + self.log.warning( + "[TNC] 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 = static.RX_FRAME_BUFFER.find(self.data_frame_bof) - eof_position = static.RX_FRAME_BUFFER.find(self.data_frame_eof) + bof_position = ARQ.rx_frame_buffer.find(self.data_frame_bof) + eof_position = ARQ.rx_frame_buffer.find(self.data_frame_eof) # get total bytes per transmission information as soon we received a frame with a BOF @@ -854,7 +884,7 @@ def arq_data_received( if ( bof_position >= 0 and eof_position > 0 - and None not in static.RX_BURST_BUFFER + and None not in ARQ.rx_burst_buffer ): self.log.debug( "[TNC] arq_data_received:", @@ -865,14 +895,14 @@ def arq_data_received( self.rx_frame_eof_received = True # Extract raw data from buffer - payload = static.RX_FRAME_BUFFER[ + payload = 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 - static.TOTAL_BYTES = frame_length + ARQ.total_bytes = frame_length # 8:9 = compression factor data_frame = payload[9:] @@ -898,18 +928,19 @@ def arq_data_received( e="wrong crc", expected=data_frame_crc.hex(), received=data_frame_crc_received.hex(), - overflows=static.BUFFER_OVERFLOW_COUNTER, + overflows=AudioParam.buffer_overflow_counter, nacks=self.frame_nack_counter, duration=duration, - bytesperminute=static.ARQ_BYTES_PER_MINUTE, - compression=static.ARQ_COMPRESSION_FACTOR, + bytesperminute=ARQ.bytes_per_minute, + compression=ARQ.arq_compression_factor, data=data_frame, ) - if static.ENABLE_STATS: + if TNC.enable_stats: self.stats.push(frame_nack_counter=self.frame_nack_counter, status="wrong_crc", duration=duration) - self.log.info("[TNC] ARQ | RX | Sending NACK", finished=static.ARQ_SECONDS_UNTIL_FINISH, bytesperminute=static.ARQ_BYTES_PER_MINUTE) + self.log.info("[TNC] ARQ | RX | Sending NACK", finished=ARQ.arq_seconds_until_finish, + bytesperminute=ARQ.bytes_per_minute) self.send_burst_nack_frame(snr) # Update arq_session timestamp @@ -919,19 +950,39 @@ def arq_data_received( self.arq_cleanup() def arq_extract_statistics_from_data_frame(self, bof_position, eof_position): - payload = static.RX_FRAME_BUFFER[ + payload = 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 - static.TOTAL_BYTES = frame_length + 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) - static.ARQ_COMPRESSION_FACTOR = compression_factor / 10 + ARQ.arq_compression_factor = compression_factor / 10 self.calculate_transfer_rate_rx( - self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER) + self.rx_start_of_transmission, len(ARQ.rx_frame_buffer) ) + 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 [ModemParam.channel_busy_slot]: + self.log.warning( + "[TNC] busy slot detection", + slots=ModemParam.channel_busy_slot, + mode_slots=mode_slots, + ) + return False + + else: + return True + def arq_calculate_speed_level(self, snr): self.frame_received_counter += 1 # try increasing speed level only if we had two successful decodes @@ -941,148 +992,155 @@ def arq_calculate_speed_level(self, snr): # 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 static.SNR >= self.snr_list[new_speed_level]: + if ModemParam.snr >= self.snr_list[new_speed_level]: self.speed_level = new_speed_level + + else: self.log.info("[TNC] ARQ | increasing speed level not possible because of SNR limit", - given_snr=static.SNR, + given_snr=ModemParam.snr, needed_snr=self.snr_list[new_speed_level] ) - static.ARQ_SPEED_LEVEL = self.speed_level + + # calculate if speed level fits to busy condition + if not self.check_if_mode_fits_to_busy_slot(): + self.speed_level = 0 + + ARQ.arq_speed_level = self.speed_level # Update modes we are listening to self.set_listening_modes(False, True, self.mode_list[self.speed_level]) def arq_process_received_data_frame(self, data_frame, snr): - """ - + """ - """ - # transmittion duration - duration = time.time() - self.rx_start_of_transmission - self.calculate_transfer_rate_rx( - self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER) - ) - self.log.info("[TNC] ARQ | RX | DATA FRAME SUCCESSFULLY RECEIVED", nacks=self.frame_nack_counter, - bytesperminute=static.ARQ_BYTES_PER_MINUTE, total_bytes=static.TOTAL_BYTES, duration=duration) - # Decompress the data frame - data_frame_decompressed = lzma.decompress(data_frame) - static.ARQ_COMPRESSION_FACTOR = len(data_frame_decompressed) / len( - data_frame - ) - data_frame = data_frame_decompressed + """ + # transmittion duration + duration = time.time() - self.rx_start_of_transmission + self.calculate_transfer_rate_rx( + self.rx_start_of_transmission, len(ARQ.rx_frame_buffer) + ) + self.log.info("[TNC] ARQ | RX | DATA FRAME SUCCESSFULLY RECEIVED", nacks=self.frame_nack_counter, + bytesperminute=ARQ.bytes_per_minute, total_bytes=ARQ.total_bytes, duration=duration) - self.transmission_uuid = str(uuid.uuid4()) - timestamp = int(time.time()) + # Decompress the data frame + data_frame_decompressed = lzma.decompress(data_frame) + ARQ.arq_compression_factor = len(data_frame_decompressed) / len( + data_frame + ) + data_frame = data_frame_decompressed - # Re-code data_frame in base64, UTF-8 for JSON UI communication. - base64_data = base64.b64encode(data_frame).decode("UTF-8") + self.transmission_uuid = str(uuid.uuid4()) + timestamp = int(time.time()) - # 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(static.RX_BUFFER_SIZE) - else: - # if full, free space by getting an item - self.log.info( - "[TNC] ARQ | RX | RX_BUFFER FULL - dropping old data", - buffer_size=RX_BUFFER.qsize(), - maxsize=int(static.RX_BUFFER_SIZE) - ) - RX_BUFFER.get() + # Re-code data_frame in base64, UTF-8 for JSON UI communication. + base64_data = base64.b64encode(data_frame).decode("UTF-8") - # add item to RX_BUFFER + # 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(ARQ.rx_buffer_size) + else: + # if full, free space by getting an item self.log.info( - "[TNC] ARQ | RX | saving data to rx buffer", - buffer_size=RX_BUFFER.qsize() + 1, - maxsize=RX_BUFFER.maxsize + "[TNC] ARQ | RX | RX_BUFFER FULL - dropping old data", + buffer_size=RX_BUFFER.qsize(), + maxsize=int(ARQ.rx_buffer_size) ) + RX_BUFFER.get() + + # add item to RX_BUFFER + self.log.info( + "[TNC] ARQ | RX | saving data to rx buffer", + buffer_size=RX_BUFFER.qsize() + 1, + maxsize=RX_BUFFER.maxsize + ) + try: + RX_BUFFER.put( + [ + self.transmission_uuid, + timestamp, + Station.dxcallsign, + Station.dxgrid, + base64_data, + ] + ) + 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( + "[TNC] ARQ | RX | error occurred when saving data!", + e=e, + uuid=self.transmission_uuid, + timestamp=timestamp, + dxcall=Station.dxcallsign, + dxgrid=Station.dxgrid, + data=base64_data + ) + + if ARQ.arq_save_to_folder: try: - RX_BUFFER.put( - [ - self.transmission_uuid, - timestamp, - static.DXCALLSIGN, - static.DXGRID, - base64_data, - ] + self.save_data_to_folder( + self.transmission_uuid, + timestamp, + self.mycallsign, + Station.dxcallsign, + Station.dxgrid, + data_frame ) 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( - "[TNC] ARQ | RX | error occurred when saving data!", + "[TNC] ARQ | RX | can't save file to folder", e=e, uuid=self.transmission_uuid, timestamp=timestamp, - dxcall=static.DXCALLSIGN, - dxgrid=static.DXGRID, + dxcall=Station.dxcallsign, + dxgrid=Station.dxgrid, data=base64_data ) - if static.ARQ_SAVE_TO_FOLDER: - try: - self.save_data_to_folder( - self.transmission_uuid, - timestamp, - self.mycallsign, - static.DXCALLSIGN, - static.DXGRID, - data_frame - ) - except Exception as e: - self.log.error( - "[TNC] ARQ | RX | can't save file to folder", - e=e, - uuid=self.transmission_uuid, - timestamp=timestamp, - dxcall=static.DXCALLSIGN, - dxgrid=static.DXGRID, - data=base64_data - ) - - self.send_data_to_socket_queue( - freedata="tnc-message", - arq="transmission", - status="received", - uuid=self.transmission_uuid, - percent=static.ARQ_TRANSMISSION_PERCENT, - bytesperminute=static.ARQ_BYTES_PER_MINUTE, - compression=static.ARQ_COMPRESSION_FACTOR, - timestamp=timestamp, - finished=0, - mycallsign=str(self.mycallsign, "UTF-8"), - dxcallsign=str(static.DXCALLSIGN, "UTF-8"), - dxgrid=str(static.DXGRID, "UTF-8"), - data=base64_data, - irs=helpers.bool_to_string(self.is_IRS) - ) + self.send_data_to_socket_queue( + freedata="tnc-message", + arq="transmission", + status="received", + uuid=self.transmission_uuid, + percent=ARQ.arq_transmission_percent, + bytesperminute=ARQ.bytes_per_minute, + compression=ARQ.arq_compression_factor, + timestamp=timestamp, + finished=0, + mycallsign=str(self.mycallsign, "UTF-8"), + dxcallsign=str(Station.dxcallsign, "UTF-8"), + dxgrid=str(Station.dxgrid, "UTF-8"), + data=base64_data, + irs=helpers.bool_to_string(self.is_IRS) + ) - if static.ENABLE_STATS: - duration = time.time() - self.rx_start_of_transmission - self.stats.push(frame_nack_counter=self.frame_nack_counter, status="received", duration=duration) + if TNC.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( - "[TNC] ARQ | RX | SENDING DATA FRAME ACK") + self.log.info( + "[TNC] 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(static.RX_FRAME_BUFFER) - ) + 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(ARQ.rx_frame_buffer) + ) - self.log.info( - "[TNC] | RX | DATACHANNEL [" - + str(self.mycallsign, "UTF-8") - + "]<< >>[" - + str(static.DXCALLSIGN, "UTF-8") - + "]", - snr=snr, - ) + self.log.info( + "[TNC] | RX | DATACHANNEL [" + + str(self.mycallsign, "UTF-8") + + "]<< >>[" + + str(Station.dxcallsign, "UTF-8") + + "]", + snr=snr, + ) def arq_transmit(self, data_out: bytes, mode: int, n_frames_per_burst: int): """ @@ -1094,6 +1152,7 @@ def arq_transmit(self, data_out: bytes, mode: int, n_frames_per_burst: int): n_frames_per_burst:int: """ + # set signalling modes we want to listen to # we are in an ongoing arq transmission, so we don't need sig0 actually modem.RECEIVE_SIG0 = False @@ -1103,25 +1162,25 @@ def arq_transmit(self, data_out: bytes, mode: int, n_frames_per_burst: int): # Maximum number of retries to send before declaring a frame is lost # save len of data_out to TOTAL_BYTES for our statistics - static.TOTAL_BYTES = len(data_out) + 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) - static.ARQ_COMPRESSION_FACTOR = np.clip(compression_factor, 0, 255) - compression_factor = bytes([int(static.ARQ_COMPRESSION_FACTOR * 10)]) + ARQ.arq_compression_factor = np.clip(compression_factor, 0, 255) + compression_factor = bytes([int(ARQ.arq_compression_factor * 10)]) self.send_data_to_socket_queue( freedata="tnc-message", arq="transmission", status="transmitting", uuid=self.transmission_uuid, - percent=static.ARQ_TRANSMISSION_PERCENT, - bytesperminute=static.ARQ_BYTES_PER_MINUTE, - compression=static.ARQ_COMPRESSION_FACTOR, - finished=static.ARQ_SECONDS_UNTIL_FINISH, + percent=ARQ.arq_transmission_percent, + bytesperminute=ARQ.bytes_per_minute, + compression=ARQ.arq_compression_factor, + finished=ARQ.arq_seconds_until_finish, mycallsign=str(self.mycallsign, 'UTF-8'), dxcallsign=str(self.dxcallsign, 'UTF-8'), irs=helpers.bool_to_string(self.is_IRS) @@ -1129,7 +1188,7 @@ def arq_transmit(self, data_out: bytes, mode: int, n_frames_per_burst: int): self.log.info( "[TNC] | TX | DATACHANNEL", - Bytes=static.TOTAL_BYTES, + Bytes=ARQ.total_bytes, ) data_out = data_frame_compressed @@ -1153,10 +1212,12 @@ def arq_transmit(self, data_out: bytes, mode: int, n_frames_per_burst: int): ) self.log.debug("[TNC] frame raw data:", data=data_out) # Initial bufferposition is 0 - bufferposition = bufferposition_end = 0 + bufferposition = 0 + bufferposition_end = 0 + bufferposition_burst_start = 0 # Iterate through data_out buffer - while not self.data_frame_ack_received and static.ARQ_STATE: + while not self.data_frame_ack_received and ARQ.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: @@ -1165,7 +1226,7 @@ def arq_transmit(self, data_out: bytes, mode: int, n_frames_per_burst: int): self.speed_level = min(self.speed_level, len(self.mode_list) - 1) self.speed_level = max(self.speed_level, 0) - static.ARQ_SPEED_LEVEL = self.speed_level + ARQ.arq_speed_level = self.speed_level data_mode = self.mode_list[self.speed_level] self.log.debug( @@ -1178,60 +1239,86 @@ def arq_transmit(self, data_out: bytes, mode: int, n_frames_per_burst: int): # Payload information payload_per_frame = modem.get_bytes_per_frame(data_mode) - 2 - # Append data frames with n_frames_per_burst to tempbuffer - # TODO: this part needs a complete rewrite! - # n_frames_per_burst = 1 is working - - arqheader = bytearray() - # arqheader[:1] = bytes([FR_TYPE.BURST_01.value + i]) - 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("[TNC] ----- 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("[TNC] ----- data buffer offset:", iss_buffer_pos=bufferposition, irs_bufferposition=self.irs_buffer_position) - # only adjust buffer position for experimental versions - if 'exp' in static.VERSION: - self.log.warning("[TNC] ----- data adjustment disabled!") - # 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 + self.log.info("[TNC] early buffer info", + bufferposition=bufferposition, + bufferposition_end=bufferposition_end, + bufferposition_burst_start=bufferposition_burst_start + ) - # Pad the last bytes of a frame + # 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: - 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 + n_frames_per_burst = 1 + self.log.info("[TNC] calculated frames_per_burst:", n=n_frames_per_burst) - tempbuffer = [frame] - self.log.debug("[TNC] tempbuffer:", tempbuffer=tempbuffer) - self.log.info( - "[TNC] ARQ | TX | FRAMES", - mode=FREEDV_MODE(data_mode).name, - fpb=n_frames_per_burst, - retry=self.tx_n_retry_of_burst, - ) + tempbuffer = [] + self.rpt_request_buffer = [] + # Append data frames with n_frames_per_burst to tempbuffer + for n_frame in range(0, 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("[TNC] ----- 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("[TNC] ----- data buffer offset:", iss_buffer_pos=bufferposition, + irs_bufferposition=self.irs_buffer_position) + # only adjust buffer position for experimental versions + if 'exp' in TNC.version: + self.log.warning("[TNC] ----- data adjustment disabled!") + # 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("[TNC] tempbuffer:", tempbuffer=tempbuffer) + self.log.info( + "[TNC] ARQ | TX | FRAMES", + mode=FREEDV_MODE(data_mode).name, + fpb=n_frames_per_burst, + retry=self.tx_n_retry_of_burst, + ) - for t_buf_item in tempbuffer: - self.enqueue_frame_for_tx([t_buf_item], c2_mode=data_mode) + self.enqueue_frame_for_tx(tempbuffer, c2_mode=data_mode) # After transmission finished, wait for an ACK or RPT frame while ( - static.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 + ARQ.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) @@ -1241,21 +1328,34 @@ def arq_transmit(self, data_out: bytes, mode: int, n_frames_per_burst: int): self.tx_n_retry_of_burst = 0 # reset retries self.log.debug( "[TNC] arq_transmit: Received BURST ACK. Sending next chunk." - , irs_snr=self.burst_ack_snr) + , 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.burst_nack: - self.burst_nack = False # reset nack state - if self.data_frame_ack_received: self.log.debug( "[TNC] 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( + "[TNC] 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 - # static.ARQ_STATE == "DATA" --> when stopping transmission manually - if not static.ARQ_STATE: + # ARQ.arq_state == "DATA" --> when stopping transmission manually + if not ARQ.arq_state: self.log.debug( "[TNC] arq_transmit: ARQ State changed to FALSE. Breaking retry loop." ) @@ -1269,7 +1369,7 @@ def arq_transmit(self, data_out: bytes, mode: int, n_frames_per_burst: int): "[TNC] ATTEMPT:", retry=self.tx_n_retry_of_burst, maxretries=self.tx_n_max_retries_per_burst, - overflows=static.BUFFER_OVERFLOW_COUNTER, + overflows=AudioParam.buffer_overflow_counter, ) # update buffer position @@ -1285,10 +1385,10 @@ def arq_transmit(self, data_out: bytes, mode: int, n_frames_per_burst: int): arq="transmission", status="transmitting", uuid=self.transmission_uuid, - percent=static.ARQ_TRANSMISSION_PERCENT, - bytesperminute=static.ARQ_BYTES_PER_MINUTE, - compression=static.ARQ_COMPRESSION_FACTOR, - finished=static.ARQ_SECONDS_UNTIL_FINISH, + percent=ARQ.arq_transmission_percent, + bytesperminute=ARQ.bytes_per_minute, + compression=ARQ.arq_compression_factor, + finished=ARQ.arq_seconds_until_finish, irs_snr=self.burst_ack_snr, mycallsign=str(self.mycallsign, 'UTF-8'), dxcallsign=str(self.dxcallsign, 'UTF-8'), @@ -1327,10 +1427,10 @@ def arq_transmit_success(self): arq="transmission", status="transmitted", uuid=self.transmission_uuid, - percent=static.ARQ_TRANSMISSION_PERCENT, - bytesperminute=static.ARQ_BYTES_PER_MINUTE, - compression=static.ARQ_COMPRESSION_FACTOR, - finished=static.ARQ_SECONDS_UNTIL_FINISH, + percent=ARQ.arq_transmission_percent, + bytesperminute=ARQ.bytes_per_minute, + compression=ARQ.arq_compression_factor, + finished=ARQ.arq_seconds_until_finish, mycallsign=str(self.mycallsign, 'UTF-8'), dxcallsign=str(self.dxcallsign, 'UTF-8'), irs=helpers.bool_to_string(self.is_IRS) @@ -1338,10 +1438,10 @@ def arq_transmit_success(self): self.log.info( "[TNC] ARQ | TX | DATA TRANSMITTED!", - BytesPerMinute=static.ARQ_BYTES_PER_MINUTE, - total_bytes=static.TOTAL_BYTES, - BitsPerSecond=static.ARQ_BITS_PER_SECOND, - overflows=static.BUFFER_OVERFLOW_COUNTER, + BytesPerMinute=ARQ.bytes_per_minute, + total_bytes=ARQ.total_bytes, + BitsPerSecond=ARQ.arq_bits_per_second, + overflows=AudioParam.buffer_overflow_counter, ) @@ -1357,9 +1457,9 @@ def arq_transmit_failed(self): arq="transmission", status="failed", uuid=self.transmission_uuid, - percent=static.ARQ_TRANSMISSION_PERCENT, - bytesperminute=static.ARQ_BYTES_PER_MINUTE, - compression=static.ARQ_COMPRESSION_FACTOR, + percent=ARQ.arq_transmission_percent, + bytesperminute=ARQ.bytes_per_minute, + compression=ARQ.arq_compression_factor, mycallsign=str(self.mycallsign, 'UTF-8'), dxcallsign=str(self.dxcallsign, 'UTF-8'), irs=helpers.bool_to_string(self.is_IRS) @@ -1367,7 +1467,7 @@ def arq_transmit_failed(self): self.log.info( "[TNC] ARQ | TX | TRANSMISSION FAILED OR TIME OUT!", - overflows=static.BUFFER_OVERFLOW_COUNTER, + overflows=AudioParam.buffer_overflow_counter, ) self.stop_transmission() @@ -1384,19 +1484,18 @@ def burst_ack_nack_received(self, data_in: bytes) -> None: """ # Process data only if we are in ARQ and BUSY state - if static.ARQ_STATE: - static.DXGRID = b'------' + if ARQ.arq_state: + Station.dxgrid = b'------' helpers.add_to_heard_stations( self.dxcallsign, - static.DXGRID, + Station.dxgrid, "DATA-CHANNEL", - static.SNR, - static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY, + ModemParam.snr, + ModemParam.frequency_offset, + HamlibParam.hamlib_frequency, ) frametype = int.from_bytes(bytes(data_in[:1]), "big") - desc = "ack" 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) @@ -1418,7 +1517,7 @@ def burst_ack_nack_received(self, data_in: bytes) -> None: # Increment burst nack counter self.burst_nack_counter += 1 self.burst_ack_snr = 'NaN' - self.irs_buffer_position = int.from_bytes(data_in[4:8], "big") + self.irs_buffer_position = int.from_bytes(data_in[5:9], "big") self.log.warning( "[TNC] ARQ | TX | Burst NACK received", @@ -1433,22 +1532,22 @@ def burst_ack_nack_received(self, data_in: bytes) -> None: # self.log.info("SNR ON IRS", snr=self.burst_ack_snr) self.speed_level = int.from_bytes(bytes(data_in[3:4]), "big") - static.ARQ_SPEED_LEVEL = self.speed_level + ARQ.arq_speed_level = self.speed_level def frame_ack_received( self, data_in: bytes # pylint: disable=unused-argument ) -> None: """Received an ACK for a transmitted frame""" # Process data only if we are in ARQ and BUSY state - if static.ARQ_STATE: - static.DXGRID = b'------' + if ARQ.arq_state: + Station.dxgrid = b'------' helpers.add_to_heard_stations( - static.DXCALLSIGN, - static.DXGRID, + Station.dxcallsign, + Station.dxgrid, "DATA-CHANNEL", - static.SNR, - static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY, + ModemParam.snr, + ModemParam.frequency_offset, + HamlibParam.hamlib_frequency, ) # Force data loops of TNC to stop and continue with next frame self.data_frame_ack_received = True @@ -1470,30 +1569,30 @@ def frame_nack_received( arq="transmission", status="failed", uuid=self.transmission_uuid, - percent=static.ARQ_TRANSMISSION_PERCENT, - bytesperminute=static.ARQ_BYTES_PER_MINUTE, + percent=ARQ.arq_transmission_percent, + bytesperminute=ARQ.bytes_per_minute, mycallsign=str(self.mycallsign, 'UTF-8'), dxcallsign=str(self.dxcallsign, 'UTF-8'), irs=helpers.bool_to_string(self.is_IRS) ) - static.DXGRID = b'------' + Station.dxgrid = b'------' helpers.add_to_heard_stations( - static.DXCALLSIGN, - static.DXGRID, + Station.dxcallsign, + Station.dxgrid, "DATA-CHANNEL", - static.SNR, - static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY, + ModemParam.snr, + ModemParam.frequency_offset, + HamlibParam.hamlib_frequency, ) self.send_data_to_socket_queue( freedata="tnc-message", arq="transmission", status="failed", uuid=self.transmission_uuid, - percent=static.ARQ_TRANSMISSION_PERCENT, - bytesperminute=static.ARQ_BYTES_PER_MINUTE, - compression=static.ARQ_COMPRESSION_FACTOR, + percent=ARQ.arq_transmission_percent, + bytesperminute=ARQ.bytes_per_minute, + compression=ARQ.arq_compression_factor, mycallsign=str(self.mycallsign, 'UTF-8'), dxcallsign=str(self.dxcallsign, 'UTF-8'), irs=helpers.bool_to_string(self.is_IRS) @@ -1512,35 +1611,50 @@ def burst_rpt_received(self, data_in: bytes): """ # Only process data if we are in ARQ and BUSY state - if not static.ARQ_STATE or static.TNC_STATE != "BUSY": + if not ARQ.arq_state or TNC.tnc_state != "BUSY": return - static.DXGRID = b'------' + Station.dxgrid = b'------' helpers.add_to_heard_stations( - static.DXCALLSIGN, - static.DXGRID, + Station.dxcallsign, + Station.dxgrid, "DATA-CHANNEL", - static.SNR, - static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY, + ModemParam.snr, + ModemParam.frequency_offset, + HamlibParam.hamlib_frequency, ) - self.rpt_request_received = True + self.log.info("[TNC] ARQ REPEAT RECEIVED") + + # self.rpt_request_received = True # Update data_channel timestamp self.data_channel_last_received = int(time.time()) - self.rpt_request_buffer = [] + # self.rpt_request_buffer = [] - missing_area = bytes(data_in[3:12]) # 1:9 + missing_area = bytes(data_in[2:12]) # 1:9 + missing_area = missing_area.strip(b"\x00") + print(missing_area) + print(self.rpt_request_buffer) - 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]) + tempbuffer_rptframes = [] + for i in range(0, 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("[TNC] SENDING REPEAT....") + data_mode = self.mode_list[self.speed_level] + self.enqueue_frame_for_tx(tempbuffer_rptframes, c2_mode=data_mode) + + # 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 SESSION HANDLER ############################################################################################################ def arq_session_handler(self, mycallsign, dxcallsign, attempts) -> bool: """ - Create a session with `static.DXCALLSIGN` and wait until the session is open. + Create a session with `Station.dxcallsign` and wait until the session is open. Returns: True if the session was opened successfully @@ -1552,8 +1666,8 @@ def arq_session_handler(self, mycallsign, dxcallsign, attempts) -> bool: self.mycallsign = mycallsign self.dxcallsign = dxcallsign - static.DXCALLSIGN = self.dxcallsign - static.DXCALLSIGN_CRC = helpers.get_crc_24(self.dxcallsign) + Station.dxcallsign = self.dxcallsign + Station.dxcallsign_crc = helpers.get_crc_24(self.dxcallsign) # TODO: we need to check this, maybe placing it to class init self.datachannel_timeout = False @@ -1563,11 +1677,11 @@ def arq_session_handler(self, mycallsign, dxcallsign, attempts) -> bool: + "]>> <<[" + str(self.dxcallsign, "UTF-8") + "]", - state=static.ARQ_SESSION_STATE, + state=ARQ.arq_session_state, ) # Let's check if we have a busy channel - if static.CHANNEL_BUSY: + if ModemParam.channel_busy: self.log.warning("[TNC] Channel busy, waiting until free...") self.send_data_to_socket_queue( freedata="tnc-message", @@ -1579,13 +1693,13 @@ def arq_session_handler(self, mycallsign, dxcallsign, attempts) -> bool: # wait while timeout not reached and our busy state is busy channel_busy_timeout = time.time() + 15 - while static.CHANNEL_BUSY and time.time() < channel_busy_timeout: + while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): threading.Event().wait(0.01) # if channel busy timeout reached stop connecting if time.time() > channel_busy_timeout: self.log.warning("[TNC] Channel busy, try again later...") - static.ARQ_SESSION_STATE = "failed" + ARQ.arq_session_state = "failed" self.send_data_to_socket_queue( freedata="tnc-message", arq="session", @@ -1599,9 +1713,9 @@ def arq_session_handler(self, mycallsign, dxcallsign, attempts) -> bool: self.open_session() # wait until data channel is open - while not static.ARQ_SESSION and not self.arq_session_timeout: + while not ARQ.arq_session and not self.arq_session_timeout: threading.Event().wait(0.01) - static.ARQ_SESSION_STATE = "connecting" + ARQ.arq_session_state = "connecting" self.send_data_to_socket_queue( freedata="tnc-message", arq="session", @@ -1609,8 +1723,8 @@ def arq_session_handler(self, mycallsign, dxcallsign, attempts) -> bool: mycallsign=str(self.mycallsign, 'UTF-8'), dxcallsign=str(self.dxcallsign, 'UTF-8'), ) - if static.ARQ_SESSION and static.ARQ_SESSION_STATE == "connected": - # static.ARQ_SESSION_STATE = "connected" + if ARQ.arq_session and ARQ.arq_session_state == "connected": + # ARQ.arq_session_state = "connected" self.send_data_to_socket_queue( freedata="tnc-message", arq="session", @@ -1628,9 +1742,9 @@ def arq_session_handler(self, mycallsign, dxcallsign, attempts) -> bool: + "]", attempts=self.session_connect_max_retries, # Adjust for 0-based for user display reason="maximum connection attempts reached", - state=static.ARQ_SESSION_STATE, + state=ARQ.arq_session_state, ) - static.ARQ_SESSION_STATE = "failed" + ARQ.arq_session_state = "failed" self.send_data_to_socket_queue( freedata="tnc-message", arq="session", @@ -1650,7 +1764,7 @@ def open_session(self) -> bool: False if the session open request failed """ self.IS_ARQ_SESSION_MASTER = True - static.ARQ_SESSION_STATE = "connecting" + ARQ.arq_session_state = "connecting" # create a random session id self.session_id = np.random.bytes(1) @@ -1658,11 +1772,11 @@ def open_session(self) -> bool: connection_frame = bytearray(self.length_sig0_frame) connection_frame[:1] = bytes([FR_TYPE.ARQ_SESSION_OPEN.value]) connection_frame[1:2] = self.session_id - connection_frame[2:5] = static.DXCALLSIGN_CRC - connection_frame[5:8] = static.MYCALLSIGN_CRC + connection_frame[2:5] = Station.dxcallsign_crc + connection_frame[5:8] = Station.mycallsign_crc connection_frame[8:14] = helpers.callsign_to_bytes(self.mycallsign) - while not static.ARQ_SESSION: + while not ARQ.arq_session: threading.Event().wait(0.01) for attempt in range(self.session_connect_max_retries): self.log.info( @@ -1672,7 +1786,7 @@ def open_session(self) -> bool: + str(self.dxcallsign, "UTF-8") + "]", a=f"{str(attempt + 1)}/{str(self.session_connect_max_retries)}", - state=static.ARQ_SESSION_STATE, + state=ARQ.arq_session_state, ) self.send_data_to_socket_queue( @@ -1685,19 +1799,19 @@ def open_session(self) -> bool: dxcallsign=str(self.dxcallsign, 'UTF-8'), ) - self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.datac0.value, copies=1, repeat_delay=0) + 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 `static.ARQ_SESSION` + # Wait for a time, looking to see if `ARQ.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 static.ARQ_SESSION: + if ARQ.arq_session: return True # Stop waiting and interrupt if data channel is getting closed while opening - if static.ARQ_SESSION_STATE == "disconnecting": + if ARQ.arq_session_state == "disconnecting": # disabled this session close as its called twice # self.close_session() return False @@ -1705,11 +1819,11 @@ def open_session(self) -> bool: # 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 static.ARQ_SESSION: + if not ARQ.arq_session: self.close_session() return False - # Given the while condition, it will only exit when `static.ARQ_SESSION` is True + # Given the while condition, it will only exit when `ARQ.arq_session` is True self.send_data_to_socket_queue( freedata="tnc-message", arq="session", @@ -1727,7 +1841,7 @@ def received_session_opener(self, data_in: bytes) -> None: data_in:bytes: """ # if we don't want to respond to calls, return False - if not static.RESPOND_TO_CALL: + if not TNC.respond_to_call: return False # ignore channel opener if already in ARQ STATE @@ -1735,31 +1849,31 @@ def received_session_opener(self, data_in: bytes) -> None: # 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 static.ARQ_SESSION and self.IS_ARQ_SESSION_MASTER: + if ARQ.arq_session and self.IS_ARQ_SESSION_MASTER: return False self.IS_ARQ_SESSION_MASTER = False - static.ARQ_SESSION_STATE = "connecting" + ARQ.arq_session_state = "connecting" # Update arq_session timestamp self.arq_session_last_received = int(time.time()) self.session_id = bytes(data_in[1:2]) - static.DXCALLSIGN_CRC = bytes(data_in[5:8]) + Station.dxcallsign_crc = bytes(data_in[5:8]) self.dxcallsign = helpers.bytes_to_callsign(bytes(data_in[8:14])) - static.DXCALLSIGN = self.dxcallsign + Station.dxcallsign = self.dxcallsign # check if callsign ssid override valid, mycallsign = helpers.check_callsign(self.mycallsign, data_in[2:5]) self.mycallsign = mycallsign - static.DXGRID = b'------' + Station.dxgrid = b'------' helpers.add_to_heard_stations( - static.DXCALLSIGN, - static.DXGRID, + Station.dxcallsign, + Station.dxgrid, "DATA-CHANNEL", - static.SNR, - static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY, + ModemParam.snr, + ModemParam.frequency_offset, + HamlibParam.hamlib_frequency, ) self.log.info( "[TNC] SESSION [" @@ -1767,10 +1881,10 @@ def received_session_opener(self, data_in: bytes) -> None: + "]>>|<<[" + str(self.dxcallsign, "UTF-8") + "]", - state=static.ARQ_SESSION_STATE, + state=ARQ.arq_session_state, ) - static.ARQ_SESSION = True - static.TNC_STATE = "BUSY" + ARQ.arq_session = True + TNC.tnc_state = "BUSY" self.send_data_to_socket_queue( freedata="tnc-message", @@ -1783,7 +1897,7 @@ def received_session_opener(self, data_in: bytes) -> None: def close_session(self) -> None: """Close the ARQ session""" - static.ARQ_SESSION_STATE = "disconnecting" + ARQ.arq_session_state = "disconnecting" self.log.info( "[TNC] SESSION [" @@ -1791,7 +1905,7 @@ def close_session(self) -> None: + "]<>[" + str(self.dxcallsign, "UTF-8") + "]", - state=static.ARQ_SESSION_STATE, + state=ARQ.arq_session_state, ) self.send_data_to_socket_queue( @@ -1803,14 +1917,14 @@ def close_session(self) -> None: ) self.IS_ARQ_SESSION_MASTER = False - static.ARQ_SESSION = False + ARQ.arq_session = False # we need to send disconnect frame before doing arq cleanup # we would lose our session id then self.send_disconnect_frame() self.arq_cleanup() - static.ARQ_SESSION_STATE = "disconnected" + ARQ.arq_session_state = "disconnected" def received_session_close(self, data_in: bytes): """ @@ -1826,16 +1940,16 @@ def received_session_close(self, data_in: bytes): # 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])) _valid_session = helpers.check_session_id(self.session_id, bytes(data_in[1:2])) - if (_valid_crc or _valid_session) and static.ARQ_SESSION_STATE not in ["disconnected"]: - static.ARQ_SESSION_STATE = "disconnected" - static.DXGRID = b'------' + if (_valid_crc or _valid_session) and ARQ.arq_session_state not in ["disconnected"]: + ARQ.arq_session_state = "disconnected" + Station.dxgrid = b'------' helpers.add_to_heard_stations( - static.DXCALLSIGN, - static.DXGRID, + Station.dxcallsign, + Station.dxgrid, "DATA-CHANNEL", - static.SNR, - static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY, + ModemParam.snr, + ModemParam.frequency_offset, + HamlibParam.hamlib_frequency, ) self.log.info( "[TNC] SESSION [" @@ -1843,7 +1957,7 @@ def received_session_close(self, data_in: bytes): + "]<>[" + str(self.dxcallsign, "UTF-8") + "]", - state=static.ARQ_SESSION_STATE, + state=ARQ.arq_session_state, ) self.send_data_to_socket_queue( @@ -1855,14 +1969,14 @@ def received_session_close(self, data_in: bytes): ) self.IS_ARQ_SESSION_MASTER = False - static.ARQ_SESSION = False + ARQ.arq_session = False self.arq_cleanup() def transmit_session_heartbeat(self) -> None: """Send ARQ sesion heartbeat while connected""" - # static.ARQ_SESSION = True - # static.TNC_STATE = "BUSY" - # static.ARQ_SESSION_STATE = "connected" + # ARQ.arq_session = True + # TNC.tnc_state = "BUSY" + # ARQ.arq_session_state = "connected" connection_frame = bytearray(self.length_sig0_frame) connection_frame[:1] = bytes([FR_TYPE.ARQ_SESSION_HB.value]) @@ -1877,7 +1991,7 @@ def transmit_session_heartbeat(self) -> None: dxcallsign=str(self.dxcallsign, 'UTF-8'), ) - self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.datac0.value, copies=1, repeat_delay=0) + 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) -> None: """ @@ -1889,16 +2003,16 @@ def received_session_heartbeat(self, data_in: bytes) -> None: # 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])) _valid_session = helpers.check_session_id(self.session_id, bytes(data_in[1:2])) - if _valid_crc or _valid_session and static.ARQ_SESSION_STATE in ["connected", "connecting"]: + if _valid_crc or _valid_session and ARQ.arq_session_state in ["connected", "connecting"]: self.log.debug("[TNC] Received session heartbeat") - static.DXGRID = b'------' + Station.dxgrid = b'------' helpers.add_to_heard_stations( self.dxcallsign, - static.DXGRID, + Station.dxgrid, "SESSION-HB", - static.SNR, - static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY, + ModemParam.snr, + ModemParam.frequency_offset, + HamlibParam.hamlib_frequency, ) self.send_data_to_socket_queue( @@ -1910,9 +2024,9 @@ def received_session_heartbeat(self, data_in: bytes) -> None: dxcallsign=str(self.dxcallsign, 'UTF-8'), ) - static.ARQ_SESSION = True - static.ARQ_SESSION_STATE = "connected" - static.TNC_STATE = "BUSY" + ARQ.arq_session = True + ARQ.arq_session_state = "connected" + TNC.tnc_state = "BUSY" # Update the timeout timestamps self.arq_session_last_received = int(time.time()) @@ -1926,9 +2040,9 @@ def received_session_heartbeat(self, data_in: bytes) -> None: if ( not self.IS_ARQ_SESSION_MASTER and not self.arq_file_transfer - and static.ARQ_SESSION_STATE != 'disconnecting' - and static.ARQ_SESSION_STATE != 'disconnected' - and static.ARQ_SESSION_STATE != 'failed' + and ARQ.arq_session_state != 'disconnecting' + and ARQ.arq_session_state != 'disconnected' + and ARQ.arq_session_state != 'failed' ): self.transmit_session_heartbeat() @@ -1967,30 +2081,30 @@ def open_dc_and_transmit( # override session connection attempts self.data_channel_max_retries = attempts - static.TNC_STATE = "BUSY" + TNC.tnc_state = "BUSY" self.arq_file_transfer = 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 static.ARQ_SESSION: - threading.Event().wait(2) + if ARQ.arq_session: + threading.Event().wait(2.5) self.datachannel_timeout = False # we need to compress data for getting a compression factor. # so we are compressing twice. This is not that nice and maybe there is another way # for calculating transmission statistics - # static.ARQ_COMPRESSION_FACTOR = len(data_out) / len(lzma.compress(data_out)) + # ARQ.arq_compression_factor = len(data_out) / len(lzma.compress(data_out)) self.arq_open_data_channel(mode, n_frames_per_burst, mycallsign) # wait until data channel is open - while not static.ARQ_STATE and not self.datachannel_timeout: + while not ARQ.arq_state and not self.datachannel_timeout: threading.Event().wait(0.01) - if static.ARQ_STATE: + if ARQ.arq_state: self.arq_transmit(data_out, mode, n_frames_per_burst) return True @@ -2014,14 +2128,14 @@ def arq_open_data_channel( self.is_IRS = False # init a new random session id if we are not in an arq session - if not static.ARQ_SESSION: + if not ARQ.arq_session: # self.session_id = randbytes(1) self.session_id = np.random.bytes(1) # Update data_channel timestamp self.data_channel_last_received = int(time.time()) - if static.LOW_BANDWIDTH_MODE: + if TNC.low_bandwidth_mode: frametype = bytes([FR_TYPE.ARQ_DC_OPEN_N.value]) self.log.debug("[TNC] Requesting low bandwidth mode") @@ -2031,13 +2145,13 @@ def arq_open_data_channel( connection_frame = bytearray(self.length_sig0_frame) connection_frame[:1] = frametype - connection_frame[1:4] = static.DXCALLSIGN_CRC - connection_frame[4:7] = static.MYCALLSIGN_CRC + connection_frame[1:4] = Station.dxcallsign_crc + connection_frame[4:7] = Station.mycallsign_crc connection_frame[7:13] = helpers.callsign_to_bytes(mycallsign) # connection_frame[13:14] = bytes([n_frames_per_burst]) connection_frame[13:14] = self.session_id - while not static.ARQ_STATE: + while not ARQ.arq_state: threading.Event().wait(0.01) for attempt in range(self.data_channel_max_retries): @@ -2046,7 +2160,7 @@ def arq_open_data_channel( arq="transmission", status="opening", mycallsign=str(mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign,'UTF-8'), + dxcallsign=str(self.dxcallsign, 'UTF-8'), irs=helpers.bool_to_string(self.is_IRS) ) @@ -2060,7 +2174,7 @@ def arq_open_data_channel( ) # Let's check if we have a busy channel and if we are not in a running arq session. - if static.CHANNEL_BUSY and not static.ARQ_STATE: + if ModemParam.channel_busy and not ARQ.arq_state: self.log.warning("[TNC] Channel busy, waiting until free...") self.send_data_to_socket_queue( freedata="tnc-message", @@ -2072,19 +2186,19 @@ def arq_open_data_channel( ) # wait while timeout not reached and our busy state is busy - channel_busy_timeout = time.time() + 10 - while static.CHANNEL_BUSY and time.time() < channel_busy_timeout: + channel_busy_timeout = time.time() + 5 + while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): threading.Event().wait(0.01) - self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.datac0.value, copies=1, repeat_delay=0) + self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) - timeout = time.time() + 3 + (static.TX_DELAY/1000 * 2) + timeout = time.time() + self.duration_sig1_frame * 3 + (ModemParam.tx_delay / 1000 * 2) while time.time() < timeout: threading.Event().wait(0.01) # Stop waiting if data channel is opened - if static.ARQ_STATE: + if ARQ.arq_state: return True - if static.TNC_STATE in ["IDLE"]: + if TNC.tnc_state in ["IDLE"]: return False # `data_channel_max_retries` attempts have been sent. Aborting attempt & cleaning up @@ -2099,9 +2213,9 @@ def arq_open_data_channel( status="failed", reason="unknown", uuid=self.transmission_uuid, - percent=static.ARQ_TRANSMISSION_PERCENT, - bytesperminute=static.ARQ_BYTES_PER_MINUTE, - compression=static.ARQ_COMPRESSION_FACTOR, + percent=ARQ.arq_transmission_percent, + bytesperminute=ARQ.bytes_per_minute, + compression=ARQ.arq_compression_factor, mycallsign=str(self.mycallsign, 'UTF-8'), dxcallsign=str(self.dxcallsign, 'UTF-8'), irs=helpers.bool_to_string(self.is_IRS) @@ -2137,12 +2251,12 @@ def arq_received_data_channel_opener(self, data_in: bytes): # 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 static.RESPOND_TO_CALL and not static.ARQ_SESSION: + if not TNC.respond_to_call and not ARQ.arq_session: return False # stop processing if not in arq session, but tnc state is busy and we have a different session id # use-case we get a connection request while connecting to another station - if not static.ARQ_SESSION and static.TNC_STATE in ["BUSY"] and data_in[13:14] != self.session_id: + if not ARQ.arq_session and TNC.tnc_state in ["BUSY"] and data_in[13:14] != self.session_id: return False self.arq_file_transfer = True @@ -2155,14 +2269,14 @@ def arq_received_data_channel_opener(self, data_in: bytes): # 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 static.ARQ_STATE and not self.is_IRS: + if ARQ.arq_state and not self.is_IRS: return False self.is_IRS = True - static.DXCALLSIGN_CRC = bytes(data_in[4:7]) + Station.dxcallsign_crc = bytes(data_in[4:7]) self.dxcallsign = helpers.bytes_to_callsign(bytes(data_in[7:13])) - static.DXCALLSIGN = self.dxcallsign + Station.dxcallsign = self.dxcallsign self.send_data_to_socket_queue( freedata="tnc-message", @@ -2184,7 +2298,7 @@ def arq_received_data_channel_opener(self, data_in: bytes): # ISS(n) <-> IRS(w) # ISS(n) <-> IRS(n) - if frametype == FR_TYPE.ARQ_DC_OPEN_W.value and not static.LOW_BANDWIDTH_MODE: + if frametype == FR_TYPE.ARQ_DC_OPEN_W.value and not TNC.low_bandwidth_mode: # ISS(w) <-> IRS(w) constellation = "ISS(w) <-> IRS(w)" self.received_LOW_BANDWIDTH_MODE = False @@ -2198,7 +2312,7 @@ def arq_received_data_channel_opener(self, data_in: bytes): 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 static.LOW_BANDWIDTH_MODE: + elif frametype == FR_TYPE.ARQ_DC_OPEN_N.value and not TNC.low_bandwidth_mode: # ISS(n) <-> IRS(w) constellation = "ISS(n) <-> IRS(w)" self.received_LOW_BANDWIDTH_MODE = True @@ -2222,27 +2336,41 @@ def arq_received_data_channel_opener(self, data_in: bytes): # 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 + + # TODO: MOVE THIS TO arq_calculate_speed_level() + # calculate speed level in correlation to latest known SNR for i in range(len(self.mode_list)): - if static.SNR >= self.snr_list[i]: + if ModemParam.snr >= self.snr_list[i]: self.speed_level = i + # calculate if speed level fits to busy condition + mode_name = codec2.FREEDV_MODE(self.mode_list[self.speed_level]).name + mode_slots = codec2.FREEDV_MODE_USED_SLOTS[mode_name].value + if mode_slots in [ModemParam.channel_busy_slot]: + self.speed_level = 0 + self.log.warning( + "[TNC] busy slot detection", + slots=ModemParam.channel_busy_slot, + mode_slots=mode_slots, + ) + self.log.debug( "[TNC] calculated speed level", speed_level=self.speed_level, - given_snr=static.SNR, + given_snr=ModemParam.snr, min_snr=self.snr_list[self.speed_level], ) # Update modes we are listening to self.set_listening_modes(True, True, self.mode_list[self.speed_level]) - static.DXGRID = b'------' + Station.dxgrid = b'------' helpers.add_to_heard_stations( - static.DXCALLSIGN, - static.DXGRID, + Station.dxcallsign, + Station.dxgrid, "DATA-CHANNEL", - static.SNR, - static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY, + ModemParam.snr, + ModemParam.frequency_offset, + HamlibParam.hamlib_frequency, ) self.session_id = data_in[13:14] @@ -2261,17 +2389,17 @@ def arq_received_data_channel_opener(self, data_in: bytes): # Reset data_channel/burst timestamps self.data_channel_last_received = int(time.time()) - self.burst_last_received = int(time.time() + 6) # we might need some more time so lets increase this + 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 - static.ARQ_STATE = True - static.TNC_STATE = "BUSY" + ARQ.arq_state = True + TNC.tnc_state = "BUSY" self.reset_statistics() # Select the frame type based on the current TNC mode - if static.LOW_BANDWIDTH_MODE or self.received_LOW_BANDWIDTH_MODE: + if TNC.low_bandwidth_mode or self.received_LOW_BANDWIDTH_MODE: frametype = bytes([FR_TYPE.ARQ_DC_OPEN_ACK_N.value]) self.log.debug("[TNC] Responding with low bandwidth mode") else: @@ -2282,9 +2410,9 @@ def arq_received_data_channel_opener(self, data_in: bytes): connection_frame[:1] = frametype connection_frame[1:2] = self.session_id connection_frame[8:9] = bytes([self.speed_level]) - connection_frame[13:14] = bytes([static.ARQ_PROTOCOL_VERSION]) + connection_frame[13:14] = bytes([ARQ.arq_protocol_version]) - self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.datac0.value, copies=1, repeat_delay=0) + self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) self.send_data_to_socket_queue( freedata="tnc-message", @@ -2302,7 +2430,7 @@ def arq_received_data_channel_opener(self, data_in: bytes): + str(self.dxcallsign, "UTF-8") + "]", bandwidth="wide", - snr=static.SNR, + snr=ModemParam.snr, ) # set start of transmission for our statistics @@ -2310,7 +2438,7 @@ def arq_received_data_channel_opener(self, data_in: bytes): # Reset data_channel/burst timestamps once again for avoiding running into timeout self.data_channel_last_received = int(time.time()) - self.burst_last_received = int(time.time() + 6) # we might need some more time so lets increase this + self.burst_last_received = int(time.time() + 10) # we might need some more time so lets increase this def arq_received_channel_is_open(self, data_in: bytes) -> None: """ @@ -2320,7 +2448,7 @@ def arq_received_channel_is_open(self, data_in: bytes) -> None: """ protocol_version = int.from_bytes(bytes(data_in[13:14]), "big") - if protocol_version == static.ARQ_PROTOCOL_VERSION: + if protocol_version == ARQ.arq_protocol_version: self.send_data_to_socket_queue( freedata="tnc-message", arq="transmission", @@ -2346,14 +2474,14 @@ def arq_received_channel_is_open(self, data_in: bytes) -> None: self.speed_level = int.from_bytes(bytes(data_in[8:9]), "big") self.log.debug("[TNC] speed level selected for given SNR", speed_level=self.speed_level) # self.speed_level = len(self.mode_list) - 1 - static.DXGRID = b'------' + Station.dxgrid = b'------' helpers.add_to_heard_stations( - static.DXCALLSIGN, - static.DXGRID, + Station.dxcallsign, + Station.dxgrid, "DATA-CHANNEL", - static.SNR, - static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY, + ModemParam.snr, + ModemParam.frequency_offset, + HamlibParam.hamlib_frequency, ) self.log.info( @@ -2362,16 +2490,16 @@ def arq_received_channel_is_open(self, data_in: bytes) -> None: + "]>>|<<[" + str(self.dxcallsign, "UTF-8") + "]", - snr=static.SNR, + snr=ModemParam.snr, ) # as soon as we set ARQ_STATE to DATA, transmission starts - static.ARQ_STATE = True + ARQ.arq_state = True # Update data_channel timestamp self.data_channel_last_received = int(time.time()) else: - static.TNC_STATE = "IDLE" - static.ARQ_STATE = False + TNC.tnc_state = "IDLE" + ARQ.arq_state = False self.send_data_to_socket_queue( freedata="tnc-message", arq="transmission", @@ -2385,7 +2513,7 @@ def arq_received_channel_is_open(self, data_in: bytes) -> None: self.log.warning( "[TNC] protocol version mismatch:", received=protocol_version, - own=static.ARQ_PROTOCOL_VERSION, + own=ARQ.arq_protocol_version, ) self.stop_transmission() @@ -2402,14 +2530,14 @@ def transmit_ping(self, mycallsign: bytes, dxcallsign: bytes) -> None: # TODO: We should display a message to this effect on the UI. self.log.warning("[TNC] Missing required callsign", dxcallsign=dxcallsign) return - static.DXCALLSIGN = dxcallsign - static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN) + Station.dxcallsign = dxcallsign + Station.dxcallsign_crc = helpers.get_crc_24(Station.dxcallsign) self.send_data_to_socket_queue( freedata="tnc-message", ping="transmitting", dxcallsign=str(dxcallsign, "UTF-8"), mycallsign=str(mycallsign, "UTF-8"), - snr=str(static.SNR), + snr=str(ModemParam.snr), ) self.log.info( "[TNC] PING REQ [" @@ -2421,15 +2549,15 @@ def transmit_ping(self, mycallsign: bytes, dxcallsign: bytes) -> None: ping_frame = bytearray(self.length_sig0_frame) ping_frame[:1] = bytes([FR_TYPE.PING.value]) - ping_frame[1:4] = static.DXCALLSIGN_CRC + ping_frame[1:4] = Station.dxcallsign_crc ping_frame[4:7] = helpers.get_crc_24(mycallsign) ping_frame[7:13] = helpers.callsign_to_bytes(mycallsign) - if static.ENABLE_FSK: - self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK) + if TNC.enable_fsk: + self.log.info("[TNC] ENABLE FSK", state=TNC.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.datac0.value) + self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.sig0.value) def received_ping(self, data_in: bytes) -> None: """ @@ -2449,25 +2577,25 @@ def received_ping(self, data_in: bytes) -> None: self.log.debug("[TNC] received_ping: ping not for this station.") return - static.DXCALLSIGN_CRC = dxcallsign_crc - static.DXCALLSIGN = dxcallsign + Station.dxcallsign_crc = dxcallsign_crc + Station.dxcallsign = dxcallsign self.log.info( "[TNC] PING REQ [" + str(mycallsign, "UTF-8") + "] <<< [" + str(dxcallsign, "UTF-8") + "]", - snr=static.SNR, + snr=ModemParam.snr, ) - static.DXGRID = b'------' + Station.dxgrid = b'------' helpers.add_to_heard_stations( dxcallsign, - static.DXGRID, + Station.dxgrid, "PING", - static.SNR, - static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY, + ModemParam.snr, + ModemParam.frequency_offset, + HamlibParam.hamlib_frequency, ) self.send_data_to_socket_queue( @@ -2475,12 +2603,12 @@ def received_ping(self, data_in: bytes) -> None: ping="received", uuid=str(uuid.uuid4()), timestamp=int(time.time()), - dxgrid=str(static.DXGRID, "UTF-8"), - dxcallsign = str(dxcallsign, "UTF-8"), + dxgrid=str(Station.dxgrid, "UTF-8"), + dxcallsign=str(dxcallsign, "UTF-8"), mycallsign=str(mycallsign, "UTF-8"), - snr=str(static.SNR), + snr=str(ModemParam.snr), ) - if static.RESPOND_TO_CALL: + if TNC.respond_to_call: self.transmit_ping_ack() def transmit_ping_ack(self): @@ -2491,15 +2619,15 @@ def transmit_ping_ack(self): """ ping_frame = bytearray(self.length_sig0_frame) ping_frame[:1] = bytes([FR_TYPE.PING_ACK.value]) - ping_frame[1:4] = static.DXCALLSIGN_CRC - ping_frame[4:7] = static.MYCALLSIGN_CRC - ping_frame[7:11] = helpers.encode_grid(static.MYGRID.decode("UTF-8")) - ping_frame[13:14] = helpers.snr_to_bytes(static.SNR) + ping_frame[1:4] = Station.dxcallsign_crc + ping_frame[4:7] = Station.mycallsign_crc + ping_frame[7:11] = helpers.encode_grid(Station.mygrid.decode("UTF-8")) + ping_frame[13:14] = helpers.snr_to_bytes(ModemParam.snr) - if static.ENABLE_FSK: + if TNC.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.datac0.value) + self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.sig0.value) def received_ping_ack(self, data_in: bytes) -> None: """ @@ -2514,40 +2642,40 @@ def received_ping_ack(self, data_in: bytes) -> None: _valid, mycallsign = helpers.check_callsign(self.mycallsign, data_in[1:4]) if _valid: - static.DXGRID = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8") + Station.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="tnc-message", ping="acknowledge", uuid=str(uuid.uuid4()), timestamp=int(time.time()), - dxgrid=str(static.DXGRID, "UTF-8"), - dxcallsign = str(static.DXCALLSIGN, "UTF-8"), + dxgrid=str(Station.dxgrid, "UTF-8"), + dxcallsign=str(Station.dxcallsign, "UTF-8"), mycallsign=str(mycallsign, "UTF-8"), - snr=str(static.SNR), + snr=str(ModemParam.snr), dxsnr=str(dxsnr) ) # combined_snr = own rx snr / snr on dx side - combined_snr = f"{static.SNR}/{dxsnr}" + combined_snr = f"{ModemParam.snr}/{dxsnr}" helpers.add_to_heard_stations( - static.DXCALLSIGN, - static.DXGRID, + Station.dxcallsign, + Station.dxgrid, "PING-ACK", combined_snr, - static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY, + ModemParam.frequency_offset, + HamlibParam.hamlib_frequency, ) self.log.info( "[TNC] PING ACK [" + str(mycallsign, "UTF-8") + "] >|< [" - + str(static.DXCALLSIGN, "UTF-8") + + str(Station.dxcallsign, "UTF-8") + "]", - snr=static.SNR, + snr=ModemParam.snr, dxsnr=dxsnr, ) - static.TNC_STATE = "IDLE" + TNC.tnc_state = "IDLE" else: self.log.info( "[TNC] FOREIGN PING ACK [" @@ -2555,7 +2683,7 @@ def received_ping_ack(self, data_in: bytes) -> None: + "] ??? [" + str(bytes(data_in[4:7]), "UTF-8") + "]", - snr=static.SNR, + snr=ModemParam.snr, ) def stop_transmission(self) -> None: @@ -2564,8 +2692,8 @@ def stop_transmission(self) -> None: """ self.log.warning("[TNC] Stopping transmission!") - static.TNC_STATE = "IDLE" - static.ARQ_STATE = False + TNC.tnc_state = "IDLE" + ARQ.arq_state = False self.send_data_to_socket_queue( freedata="tnc-message", arq="transmission", @@ -2576,13 +2704,13 @@ def stop_transmission(self) -> None: stop_frame = bytearray(self.length_sig0_frame) stop_frame[:1] = bytes([FR_TYPE.ARQ_STOP.value]) - stop_frame[1:4] = static.DXCALLSIGN_CRC - stop_frame[4:7] = static.MYCALLSIGN_CRC + stop_frame[1:4] = Station.dxcallsign_crc + stop_frame[4:7] = Station.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=6, repeat_delay=0) + self.enqueue_frame_for_tx([stop_frame], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) self.arq_cleanup() @@ -2593,8 +2721,8 @@ def received_stop_transmission( Received a transmission stop """ self.log.warning("[TNC] Stopping transmission!") - static.TNC_STATE = "IDLE" - static.ARQ_STATE = False + TNC.tnc_state = "IDLE" + ARQ.arq_state = False self.send_data_to_socket_queue( freedata="tnc-message", arq="transmission", @@ -2619,14 +2747,14 @@ def run_beacon(self) -> None: try: while True: threading.Event().wait(0.5) - while static.BEACON_STATE: + while Beacon.beacon_state: if ( - not static.ARQ_SESSION + not ARQ.arq_session and not self.arq_file_transfer - and not static.BEACON_PAUSE - and not static.CHANNEL_BUSY - and static.TNC_STATE not in ["busy"] - and not static.ARQ_STATE + and not Beacon.beacon_pause + and not ModemParam.channel_busy + and TNC.tnc_state not in ["busy"] + and not ARQ.arq_state ): self.send_data_to_socket_queue( freedata="tnc-message", @@ -2641,23 +2769,23 @@ def run_beacon(self) -> None: beacon_frame = bytearray(self.length_sig0_frame) beacon_frame[:1] = bytes([FR_TYPE.BEACON.value]) beacon_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign) - beacon_frame[7:11] = helpers.encode_grid(static.MYGRID.decode("UTF-8")) + beacon_frame[7:11] = helpers.encode_grid(Station.mygrid.decode("UTF-8")) - if static.ENABLE_FSK: - self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK) + if TNC.enable_fsk: + self.log.info("[TNC] ENABLE FSK", state=TNC.enable_fsk) self.enqueue_frame_for_tx( [beacon_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value, ) else: - self.enqueue_frame_for_tx([beacon_frame], c2_mode=FREEDV_MODE.datac0.value, copies=1, + self.enqueue_frame_for_tx([beacon_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) interval_timer = time.time() + self.beacon_interval while ( time.time() < interval_timer - and static.BEACON_STATE - and not static.BEACON_PAUSE + and Beacon.beacon_state + and not Beacon.beacon_pause ): threading.Event().wait(0.01) @@ -2673,32 +2801,32 @@ def received_beacon(self, data_in: bytes) -> None: """ # here we add the received station to the heard stations buffer beacon_callsign = helpers.bytes_to_callsign(bytes(data_in[1:7])) - static.DXGRID = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8") + Station.dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8") self.send_data_to_socket_queue( freedata="tnc-message", beacon="received", uuid=str(uuid.uuid4()), timestamp=int(time.time()), dxcallsign=str(beacon_callsign, "UTF-8"), - dxgrid=str(static.DXGRID, "UTF-8"), - snr=str(static.SNR), + dxgrid=str(Station.dxgrid, "UTF-8"), + snr=str(ModemParam.snr), ) self.log.info( "[TNC] BEACON RCVD [" + str(beacon_callsign, "UTF-8") + "][" - + str(static.DXGRID, "UTF-8") + + str(Station.dxgrid, "UTF-8") + "] ", - snr=static.SNR, + snr=ModemParam.snr, ) helpers.add_to_heard_stations( beacon_callsign, - static.DXGRID, + Station.dxgrid, "BEACON", - static.SNR, - static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY, + ModemParam.snr, + ModemParam.frequency_offset, + HamlibParam.hamlib_frequency, ) def transmit_cq(self) -> None: @@ -2720,15 +2848,15 @@ def transmit_cq(self) -> None: cq_frame = bytearray(self.length_sig0_frame) cq_frame[:1] = bytes([FR_TYPE.CQ.value]) cq_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign) - cq_frame[7:11] = helpers.encode_grid(static.MYGRID.decode("UTF-8")) + cq_frame[7:11] = helpers.encode_grid(Station.mygrid.decode("UTF-8")) self.log.debug("[TNC] CQ Frame:", data=[cq_frame]) - if static.ENABLE_FSK: - self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK) + if TNC.enable_fsk: + self.log.info("[TNC] ENABLE FSK", state=TNC.enable_fsk) self.enqueue_frame_for_tx([cq_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value) else: - self.enqueue_frame_for_tx([cq_frame], c2_mode=FREEDV_MODE.datac0.value, copies=1, repeat_delay=0) + self.enqueue_frame_for_tx([cq_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) def received_cq(self, data_in: bytes) -> None: """ @@ -2742,33 +2870,33 @@ def received_cq(self, data_in: bytes) -> None: # here we add the received station to the heard stations buffer dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7])) self.log.debug("[TNC] received_cq:", dxcallsign=dxcallsign) - static.DXGRID = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8") + Station.dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8") self.send_data_to_socket_queue( freedata="tnc-message", cq="received", mycallsign=str(self.mycallsign, "UTF-8"), dxcallsign=str(dxcallsign, "UTF-8"), - dxgrid=str(static.DXGRID, "UTF-8"), + dxgrid=str(Station.dxgrid, "UTF-8"), ) self.log.info( "[TNC] CQ RCVD [" + str(dxcallsign, "UTF-8") + "][" - + str(static.DXGRID, "UTF-8") + + str(Station.dxgrid, "UTF-8") + "] ", - snr=static.SNR, + snr=ModemParam.snr, ) helpers.add_to_heard_stations( dxcallsign, - static.DXGRID, + Station.dxgrid, "CQ CQ CQ", - static.SNR, - static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY, + ModemParam.snr, + ModemParam.frequency_offset, + HamlibParam.hamlib_frequency, ) - if static.RESPOND_TO_CQ and static.RESPOND_TO_CALL: + if TNC.respond_to_cq and TNC.respond_to_call: self.transmit_qrv(dxcallsign) def transmit_qrv(self, dxcallsign: bytes) -> None: @@ -2781,9 +2909,11 @@ def transmit_qrv(self, dxcallsign: bytes) -> None: """ # 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 2s - # in 0.5s increments. - helpers.wait(randrange(0, 20, 5) / 10.0) + # 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. + self.log.info("[TNC] Waiting for QRV slot...") + helpers.wait(randrange(0, int(self.duration_sig1_frame * 4), self.duration_sig1_frame * 10 // 10.0)) self.send_data_to_socket_queue( freedata="tnc-message", qrv="transmitting", @@ -2794,14 +2924,17 @@ def transmit_qrv(self, dxcallsign: bytes) -> None: qrv_frame = bytearray(self.length_sig0_frame) qrv_frame[:1] = bytes([FR_TYPE.QRV.value]) qrv_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign) - qrv_frame[7:11] = helpers.encode_grid(static.MYGRID.decode("UTF-8")) - qrv_frame[11:12] = helpers.snr_to_bytes(static.SNR) + qrv_frame[7:11] = helpers.encode_grid(Station.mygrid.decode("UTF-8")) + qrv_frame[11:12] = helpers.snr_to_bytes(ModemParam.snr) - if static.ENABLE_FSK: - self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK) + if TNC.enable_fsk: + self.log.info("[TNC] ENABLE FSK", state=TNC.enable_fsk) self.enqueue_frame_for_tx([qrv_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value) else: - self.enqueue_frame_for_tx([qrv_frame], c2_mode=FREEDV_MODE.datac0.value, copies=1, repeat_delay=0) + if TESTMODE: + self.enqueue_frame_for_tx([qrv_frame], c2_mode=FREEDV_MODE.sig0.value, copies=2, repeat_delay=0) + else: + self.enqueue_frame_for_tx([qrv_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) def received_qrv(self, data_in: bytes) -> None: """ @@ -2812,17 +2945,17 @@ def received_qrv(self, data_in: bytes) -> None: """ # here we add the received station to the heard stations buffer dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7])) - static.DXGRID = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8") + Station.dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8") dxsnr = helpers.snr_from_bytes(data_in[11:12]) - combined_snr = f"{static.SNR}/{dxsnr}" + combined_snr = f"{ModemParam.snr}/{dxsnr}" self.send_data_to_socket_queue( freedata="tnc-message", qrv="received", dxcallsign=str(dxcallsign, "UTF-8"), - dxgrid=str(static.DXGRID, "UTF-8"), - snr=str(static.SNR), + dxgrid=str(Station.dxgrid, "UTF-8"), + snr=str(ModemParam.snr), dxsnr=str(dxsnr) ) @@ -2830,18 +2963,18 @@ def received_qrv(self, data_in: bytes) -> None: "[TNC] QRV RCVD [" + str(dxcallsign, "UTF-8") + "][" - + str(static.DXGRID, "UTF-8") + + str(Station.dxgrid, "UTF-8") + "] ", - snr=static.SNR, + snr=ModemParam.snr, dxsnr=dxsnr ) helpers.add_to_heard_stations( dxcallsign, - static.DXGRID, + Station.dxgrid, "QRV", combined_snr, - static.FREQ_OFFSET, - static.HAMLIB_FREQUENCY, + ModemParam.frequency_offset, + HamlibParam.hamlib_frequency, ) def received_is_writing(self, data_in: bytes) -> None: @@ -2882,14 +3015,14 @@ def calculate_transfer_rate_rx( transmission_percent: float """ try: - if static.TOTAL_BYTES == 0: - static.TOTAL_BYTES = 1 - static.ARQ_TRANSMISSION_PERCENT = min( + if ARQ.total_bytes == 0: + ARQ.total_bytes = 1 + ARQ.arq_transmission_percent = min( int( ( receivedbytes - * static.ARQ_COMPRESSION_FACTOR - / static.TOTAL_BYTES + * ARQ.arq_compression_factor + / ARQ.total_bytes ) * 100 ), @@ -2899,30 +3032,31 @@ def calculate_transfer_rate_rx( transmissiontime = time.time() - self.rx_start_of_transmission if receivedbytes > 0: - static.ARQ_BITS_PER_SECOND = int((receivedbytes * 8) / transmissiontime) - static.ARQ_BYTES_PER_MINUTE = int( + ARQ.arq_bits_per_second = int((receivedbytes * 8) / transmissiontime) + ARQ.bytes_per_minute = int( receivedbytes / (transmissiontime / 60) ) - static.ARQ_SECONDS_UNTIL_FINISH = int(((static.TOTAL_BYTES - receivedbytes) / (static.ARQ_BYTES_PER_MINUTE * static.ARQ_COMPRESSION_FACTOR)) * 60) -20 # offset because of frame ack/nack + ARQ.arq_seconds_until_finish = int(((ARQ.total_bytes - receivedbytes) / ( + ARQ.bytes_per_minute * ARQ.arq_compression_factor)) * 60) - 20 # offset because of frame ack/nack - speed_chart = {"snr": static.SNR, "bpm": static.ARQ_BYTES_PER_MINUTE, "timestamp": int(time.time())} + speed_chart = {"snr": ModemParam.snr, "bpm": ARQ.bytes_per_minute, "timestamp": int(time.time())} # check if data already in list - if speed_chart not in static.SPEED_LIST: - static.SPEED_LIST.append(speed_chart) + if speed_chart not in ARQ.speed_list: + ARQ.speed_list.append(speed_chart) else: - static.ARQ_BITS_PER_SECOND = 0 - static.ARQ_BYTES_PER_MINUTE = 0 - static.ARQ_SECONDS_UNTIL_FINISH = 0 + ARQ.arq_bits_per_second = 0 + ARQ.bytes_per_minute = 0 + ARQ.arq_seconds_until_finish = 0 except Exception as err: self.log.error(f"[TNC] calculate_transfer_rate_rx: Exception: {err}") - static.ARQ_TRANSMISSION_PERCENT = 0.0 - static.ARQ_BITS_PER_SECOND = 0 - static.ARQ_BYTES_PER_MINUTE = 0 + ARQ.arq_transmission_percent = 0.0 + ARQ.arq_bits_per_second = 0 + ARQ.bytes_per_minute = 0 return [ - static.ARQ_BITS_PER_SECOND, - static.ARQ_BYTES_PER_MINUTE, - static.ARQ_TRANSMISSION_PERCENT, + ARQ.arq_bits_per_second, + ARQ.bytes_per_minute, + ARQ.arq_transmission_percent, ] def reset_statistics(self) -> None: @@ -2930,13 +3064,13 @@ def reset_statistics(self) -> None: Reset statistics """ # reset ARQ statistics - static.ARQ_BYTES_PER_MINUTE_BURST = 0 - static.ARQ_BYTES_PER_MINUTE = 0 - static.ARQ_BITS_PER_SECOND_BURST = 0 - static.ARQ_BITS_PER_SECOND = 0 - static.ARQ_TRANSMISSION_PERCENT = 0 - static.TOTAL_BYTES = 0 - static.ARQ_SECONDS_UNTIL_FINISH = 0 + ARQ.bytes_per_minute_BURST = 0 + ARQ.bytes_per_minute = 0 + ARQ.arq_bits_per_second_burst = 0 + ARQ.arq_bits_per_second = 0 + ARQ.arq_transmission_percent = 0 + ARQ.total_bytes = 0 + ARQ.arq_seconds_until_finish = 0 def calculate_transfer_rate_tx( self, tx_start_of_transmission: float, sentbytes: int, tx_buffer_length: int @@ -2954,38 +3088,39 @@ def calculate_transfer_rate_tx( transmission_percent: float """ try: - static.ARQ_TRANSMISSION_PERCENT = min( + ARQ.arq_transmission_percent = min( int((sentbytes / tx_buffer_length) * 100), 100 ) transmissiontime = time.time() - tx_start_of_transmission if sentbytes > 0: - static.ARQ_BITS_PER_SECOND = int((sentbytes * 8) / transmissiontime) - static.ARQ_BYTES_PER_MINUTE = int(sentbytes / (transmissiontime / 60)) - static.ARQ_SECONDS_UNTIL_FINISH = int(((tx_buffer_length - sentbytes) / (static.ARQ_BYTES_PER_MINUTE* static.ARQ_COMPRESSION_FACTOR)) * 60 ) - + ARQ.arq_bits_per_second = int((sentbytes * 8) / transmissiontime) + ARQ.bytes_per_minute = int(sentbytes / (transmissiontime / 60)) + ARQ.arq_seconds_until_finish = int(((tx_buffer_length - sentbytes) / ( + ARQ.bytes_per_minute * ARQ.arq_compression_factor)) * 60) - speed_chart = {"snr": self.burst_ack_snr, "bpm": static.ARQ_BYTES_PER_MINUTE, "timestamp": int(time.time())} + speed_chart = {"snr": self.burst_ack_snr, "bpm": ARQ.bytes_per_minute, + "timestamp": int(time.time())} # check if data already in list - if speed_chart not in static.SPEED_LIST: - static.SPEED_LIST.append(speed_chart) + if speed_chart not in ARQ.speed_list: + ARQ.speed_list.append(speed_chart) else: - static.ARQ_BITS_PER_SECOND = 0 - static.ARQ_BYTES_PER_MINUTE = 0 - static.ARQ_SECONDS_UNTIL_FINISH = 0 + ARQ.arq_bits_per_second = 0 + ARQ.bytes_per_minute = 0 + ARQ.arq_seconds_until_finish = 0 except Exception as err: self.log.error(f"[TNC] calculate_transfer_rate_tx: Exception: {err}") - static.ARQ_TRANSMISSION_PERCENT = 0.0 - static.ARQ_BITS_PER_SECOND = 0 - static.ARQ_BYTES_PER_MINUTE = 0 + ARQ.arq_transmission_percent = 0.0 + ARQ.arq_bits_per_second = 0 + ARQ.bytes_per_minute = 0 return [ - static.ARQ_BITS_PER_SECOND, - static.ARQ_BYTES_PER_MINUTE, - static.ARQ_TRANSMISSION_PERCENT, + ARQ.arq_bits_per_second, + ARQ.bytes_per_minute, + ARQ.arq_transmission_percent, ] # ----------------------CLEANUP AND RESET FUNCTIONS @@ -3005,21 +3140,26 @@ def arq_cleanup(self) -> None: 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 - static.RX_BURST_BUFFER = [] - static.RX_FRAME_BUFFER = b"" + ARQ.rx_burst_buffer = [] + 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.RECEIVE_SIG0 = True modem.RECEIVE_SIG1 = False modem.RECEIVE_DATAC1 = False modem.RECEIVE_DATAC3 = False + modem.RECEIVE_DATAC4 = False # modem.RECEIVE_FSK_LDPC_0 = False modem.RECEIVE_FSK_LDPC_1 = False # reset buffer overflow counter - static.BUFFER_OVERFLOW_COUNTER = [0, 0, 0, 0, 0] + AudioParam.buffer_overflow_counter = [0, 0, 0, 0, 0] self.is_IRS = False self.burst_nack = False @@ -3027,7 +3167,7 @@ def arq_cleanup(self) -> None: self.frame_nack_counter = 0 self.frame_received_counter = 0 self.speed_level = len(self.mode_list) - 1 - static.ARQ_SPEED_LEVEL = self.speed_level + ARQ.arq_speed_level = self.speed_level # low bandwidth mode indicator self.received_LOW_BANDWIDTH_MODE = False @@ -3040,17 +3180,17 @@ def arq_cleanup(self) -> None: self.data_channel_max_retries = 10 # we need to keep these values if in ARQ_SESSION - if not static.ARQ_SESSION: - static.TNC_STATE = "IDLE" + if not ARQ.arq_session: + TNC.tnc_state = "IDLE" self.dxcallsign = b"AA0AA-0" - self.mycallsign = static.MYCALLSIGN + self.mycallsign = Station.mycallsign self.session_id = bytes(1) - static.SPEED_LIST = [] - static.ARQ_STATE = False + ARQ.speed_list = [] + ARQ.arq_state = False self.arq_file_transfer = False - static.BEACON_PAUSE = False + Beacon.beacon_pause = False def arq_reset_ack(self, state: bool) -> None: """ @@ -3078,24 +3218,34 @@ def set_listening_modes(self, enable_sig0: bool, enable_sig1: bool, mode: int) - modem.RECEIVE_SIG0 = enable_sig0 modem.RECEIVE_SIG1 = enable_sig1 - if mode == FREEDV_MODE.datac1.value: + 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("[TNC] Changing listening data mode", mode="datac1") - elif mode == FREEDV_MODE.datac3.value: + 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("[TNC] Changing listening data mode", mode="datac3") - elif mode == FREEDV_MODE.fsk_ldpc_1.value: + 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("[TNC] 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("[TNC] 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( "[TNC] Changing listening data mode", mode="datac1/datac3/fsk_ldpc" @@ -3123,8 +3273,8 @@ def burst_watchdog(self) -> None: # 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 static.ARQ_STATE - and static.ARQ_SESSION_STATE != "connected" + not ARQ.arq_state + and ARQ.arq_session_state != "connected" or not self.is_IRS ): return @@ -3133,14 +3283,25 @@ def burst_watchdog(self) -> None: modem_error_state = modem.get_modem_error_state() # We want to reach this state only if connected ( == return above not called ) - timeout = self.burst_last_received + self.time_list[self.speed_level] - if timeout <= time.time() or modem_error_state: - print("timeout----------------") - print(time.time() - timeout) - print(time.time() - (self.burst_last_received + self.time_list[self.speed_level])) + if self.rx_n_frames_per_burst > 1: + # uses case for IRS: reduce time for waiting by counting "None" in burst buffer + frames_left = 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 - print("-----------------------") + # make sure we don't have a 0 here for avoiding too short timeouts + if frames_left == 0: + frames_left = 1 + timeout = self.burst_last_received + (self.time_list[self.speed_level] * frames_left) + # TODO: Enable this for development + # print(f"timeout expected in:{round(timeout - time.time())} | frames left: {frames_left} of {self.rx_n_frames_per_burst} | speed level: {self.speed_level}") + if timeout <= time.time() or modem_error_state: self.log.warning( "[TNC] Burst decoding error or timeout", attempt=self.n_retries_per_burst, @@ -3149,27 +3310,45 @@ def burst_watchdog(self) -> None: modem_error_state=modem_error_state ) - # reset self.burst_last_received - self.burst_last_received = time.time() + self.time_list[self.speed_level] + print( + f"frames_per_burst {self.rx_n_frame_of_burst} / {self.rx_n_frames_per_burst}, Repeats: {self.burst_rpt_counter} Nones: {ARQ.rx_burst_buffer.count(None)}") - # 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) - static.ARQ_SPEED_LEVEL = self.speed_level + if self.rx_n_frames_per_burst > 1 and self.burst_rpt_counter < 3 and 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() - # Update modes we are listening to - self.set_listening_modes(True, True, self.mode_list[self.speed_level]) + else: - # Why not pass `snr` or `static.SNR`? - self.send_burst_nack_frame_watchdog(0) + # reset self.burst_last_received + self.burst_last_received = time.time() + self.time_list[self.speed_level] - # Update data_channel timestamp - # TODO: Disabled this one for testing. - # self.data_channel_last_received = time.time() - self.n_retries_per_burst += 1 + # 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) + ARQ.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(0, 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: # print((self.data_channel_last_received + self.time_list[self.speed_level])-time.time()) pass @@ -3183,7 +3362,7 @@ def data_channel_keep_alive_watchdog(self) -> None: DATA CHANNEL """ # and not static.ARQ_SEND_KEEP_ALIVE: - if static.ARQ_STATE and static.TNC_STATE == "BUSY": + if ARQ.arq_state and TNC.tnc_state == "BUSY": threading.Event().wait(0.01) if ( self.data_channel_last_received + self.transmission_timeout @@ -3192,7 +3371,7 @@ def data_channel_keep_alive_watchdog(self) -> None: timeleft = int((self.data_channel_last_received + self.transmission_timeout) - time.time()) if timeleft % 10 == 0: - self.log.debug("Time left until timeout", seconds=timeleft) + 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()) @@ -3204,7 +3383,7 @@ def data_channel_keep_alive_watchdog(self) -> None: "[TNC] DATA [" + str(self.mycallsign, "UTF-8") + "]<>[" - + str(static.DXCALLSIGN, "UTF-8") + + str(Station.dxcallsign, "UTF-8") + "]" ) self.send_data_to_socket_queue( @@ -3224,8 +3403,8 @@ def arq_session_keep_alive_watchdog(self) -> None: ARQ SESSION """ if ( - static.ARQ_SESSION - and static.TNC_STATE == "BUSY" + ARQ.arq_session + and TNC.tnc_state == "BUSY" and not self.arq_file_transfer ): if self.arq_session_last_received + self.arq_session_timeout > time.time(): @@ -3258,9 +3437,9 @@ def heartbeat(self) -> None: while not self.arq_file_transfer: threading.Event().wait(0.01) if ( - static.ARQ_SESSION + ARQ.arq_session and self.IS_ARQ_SESSION_MASTER - and static.ARQ_SESSION_STATE == "connected" + and ARQ.arq_session_state == "connected" # and not self.arq_file_transfer ): threading.Event().wait(1) @@ -3272,7 +3451,7 @@ def send_test_frame(self) -> None: test_frame = bytearray(126) test_frame[:1] = bytes([FR_TYPE.TEST_FRAME.value]) self.enqueue_frame_for_tx( - frame_to_tx=[test_frame], c2_mode=FREEDV_MODE.datac3.value + frame_to_tx=[test_frame], c2_mode=FREEDV_MODE.datac13.value ) def send_fec_frame(self, payload, mode) -> None: @@ -3298,12 +3477,13 @@ def send_fec_is_writing(self, mycallsign) -> None: # send burst only if channel not busy - but without waiting # otherwise burst will be dropped - if not static.CHANNEL_BUSY and not static.TRANSMITTING: + if not ModemParam.channel_busy and not TNC.transmitting: self.enqueue_frame_for_tx( - frame_to_tx=[fec_frame], c2_mode=codec2.FREEDV_MODE["datac0"].value + frame_to_tx=[fec_frame], c2_mode=codec2.FREEDV_MODE["sig0"].value ) else: return False + def save_data_to_folder(self, transmission_uuid, timestamp, diff --git a/tnc/explorer.py b/tnc/explorer.py index 4a48d9d15..9b5c54442 100644 --- a/tnc/explorer.py +++ b/tnc/explorer.py @@ -13,6 +13,8 @@ import ujson as json import structlog import static +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC + log = structlog.get_logger("explorer") @@ -32,30 +34,31 @@ def interval(self): def push(self): - frequency = 0 if static.HAMLIB_FREQUENCY is None else static.HAMLIB_FREQUENCY + frequency = 0 if HamlibParam.hamlib_frequency is None else HamlibParam.hamlib_frequency band = "USB" - callsign = str(static.MYCALLSIGN, "utf-8") - gridsquare = str(static.MYGRID, "utf-8") - version = str(static.VERSION) - bandwidth = str(static.LOW_BANDWIDTH_MODE) - beacon = str(static.BEACON_STATE) - strength = str(static.HAMLIB_STRENGTH) + callsign = str(Station.mycallsign, "utf-8") + gridsquare = str(Station.mygrid, "utf-8") + version = str(TNC.version) + bandwidth = str(TNC.low_bandwidth_mode) + beacon = str(Beacon.beacon_state) + strength = str(HamlibParam.hamlib_strength) log.info("[EXPLORER] publish", frequency=frequency, band=band, callsign=callsign, gridsquare=gridsquare, version=version, bandwidth=bandwidth) headers = {"Content-Type": "application/json"} station_data = {'callsign': callsign, 'gridsquare': gridsquare, 'frequency': frequency, 'strength': strength, 'band': band, 'version': version, 'bandwidth': bandwidth, 'beacon': beacon, "lastheard": []} - for i in static.HEARD_STATIONS: + for i in TNC.heard_stations: try: callsign = str(i[0], "UTF-8") grid = str(i[1], "UTF-8") timestamp = i[2] + frequency = i[6] try: snr = i[4].split("/")[1] except AttributeError: snr = str(i[4]) - station_data["lastheard"].append({"callsign": callsign, "grid": grid, "snr": snr, "timestamp": timestamp}) + station_data["lastheard"].append({"callsign": callsign, "grid": grid, "snr": snr, "timestamp": timestamp, "frequency": frequency}) except Exception as e: log.debug("[EXPLORER] not publishing station", e=e) diff --git a/tnc/helpers.py b/tnc/helpers.py index 85aa2f052..75c1aecfa 100644 --- a/tnc/helpers.py +++ b/tnc/helpers.py @@ -8,6 +8,7 @@ from datetime import datetime,timezone import crcengine import static +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC import structlog import numpy as np import threading @@ -130,16 +131,16 @@ def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency): Nothing """ # check if buffer empty - if len(static.HEARD_STATIONS) == 0: - static.HEARD_STATIONS.append( + if len(TNC.heard_stations) == 0: + TNC.heard_stations.append( [dxcallsign, dxgrid, int(datetime.now(timezone.utc).timestamp()), datatype, snr, offset, frequency] ) # if not, we search and update else: - for i in range(len(static.HEARD_STATIONS)): + for i in range(len(TNC.heard_stations)): # Update callsign with new timestamp - if static.HEARD_STATIONS[i].count(dxcallsign) > 0: - static.HEARD_STATIONS[i] = [ + if TNC.heard_stations[i].count(dxcallsign) > 0: + TNC.heard_stations[i] = [ dxcallsign, dxgrid, int(time.time()), @@ -150,8 +151,8 @@ def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency): ] break # Insert if nothing found - if i == len(static.HEARD_STATIONS) - 1: - static.HEARD_STATIONS.append( + if i == len(TNC.heard_stations) - 1: + TNC.heard_stations.append( [ dxcallsign, dxgrid, @@ -165,10 +166,10 @@ def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency): break -# for idx, item in enumerate(static.HEARD_STATIONS): +# for idx, item in enumerate(TNC.heard_stations): # if dxcallsign in item: # item = [dxcallsign, int(time.time())] -# static.HEARD_STATIONS[idx] = item +# TNC.heard_stations[idx] = item def callsign_to_bytes(callsign) -> bytes: @@ -305,7 +306,7 @@ def check_callsign(callsign: bytes, crc_to_check: bytes): except Exception as err: log.debug("[HLP] check_callsign: Error callsign SSID to integer:", e=err) - for ssid in static.SSID_LIST: + for ssid in Station.ssid_list: call_with_ssid = bytearray(callsign) call_with_ssid.extend("-".encode("utf-8")) call_with_ssid.extend(str(ssid).encode("utf-8")) diff --git a/tnc/main.py b/tnc/main.py index 5be8b8199..6a76a728f 100755 --- a/tnc/main.py +++ b/tnc/main.py @@ -29,6 +29,7 @@ import log_handler import modem import static +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC import structlog import explorer import json @@ -255,7 +256,7 @@ def signal_handler(sig, frame): ARGS = PARSER.parse_args() # set save to folder state for allowing downloading files to local file system - static.ARQ_SAVE_TO_FOLDER = ARGS.savetofolder + ARQ.arq_save_to_folder = ARGS.savetofolder if not ARGS.configfile: @@ -266,46 +267,46 @@ def signal_handler(sig, frame): try: mycallsign = bytes(ARGS.mycall.upper(), "utf-8") mycallsign = helpers.callsign_to_bytes(mycallsign) - static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign) - static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN) - static.SSID_LIST = ARGS.ssid_list + Station.mycallsign = helpers.bytes_to_callsign(mycallsign) + Station.mycallsign_crc = helpers.get_crc_24(Station.mycallsign) + Station.ssid_list = ARGS.ssid_list # check if own ssid is always part of ssid list - own_ssid = int(static.MYCALLSIGN.split(b"-")[1]) - if own_ssid not in static.SSID_LIST: - static.SSID_LIST.append(own_ssid) + own_ssid = int(Station.mycallsign.split(b"-")[1]) + if own_ssid not in Station.ssid_list: + Station.ssid_list.append(own_ssid) - static.MYGRID = bytes(ARGS.mygrid, "utf-8") + Station.mygrid = bytes(ARGS.mygrid, "utf-8") # check if we have an int or str as device name try: - static.AUDIO_INPUT_DEVICE = int(ARGS.audio_input_device) + AudioParam.audio_input_device = int(ARGS.audio_input_device) except ValueError: - static.AUDIO_INPUT_DEVICE = ARGS.audio_input_device + AudioParam.audio_input_device = ARGS.audio_input_device try: - static.AUDIO_OUTPUT_DEVICE = int(ARGS.audio_output_device) + AudioParam.audio_output_device = int(ARGS.audio_output_device) except ValueError: - static.AUDIO_OUTPUT_DEVICE = ARGS.audio_output_device - - static.PORT = ARGS.socket_port - static.HAMLIB_RADIOCONTROL = ARGS.hamlib_radiocontrol - static.HAMLIB_RIGCTLD_IP = ARGS.rigctld_ip - static.HAMLIB_RIGCTLD_PORT = str(ARGS.rigctld_port) - static.ENABLE_SCATTER = ARGS.send_scatter - static.ENABLE_FFT = ARGS.send_fft - static.ENABLE_FSK = ARGS.enable_fsk - static.LOW_BANDWIDTH_MODE = ARGS.low_bandwidth_mode - static.TUNING_RANGE_FMIN = ARGS.tuning_range_fmin - static.TUNING_RANGE_FMAX = ARGS.tuning_range_fmax - static.TX_AUDIO_LEVEL = ARGS.tx_audio_level - static.RESPOND_TO_CQ = ARGS.enable_respond_to_cq - static.RX_BUFFER_SIZE = ARGS.rx_buffer_size - static.ENABLE_EXPLORER = ARGS.enable_explorer - static.AUDIO_AUTO_TUNE = ARGS.enable_audio_auto_tune - static.ENABLE_STATS = ARGS.enable_stats - static.AUDIO_ENABLE_TCI = ARGS.audio_enable_tci - static.TCI_IP = ARGS.tci_ip - static.TCI_PORT = ARGS.tci_port - static.TX_DELAY = ARGS.tx_delay + AudioParam.audio_output_device = ARGS.audio_output_device + + TNC.port = ARGS.socket_port + HamlibParam.hamlib_radiocontrol = ARGS.hamlib_radiocontrol + HamlibParam.hamlib_rigctld_ip = ARGS.rigctld_ip + HamlibParam.hamlib_rigctld_port = str(ARGS.rigctld_port) + ModemParam.enable_scatter = ARGS.send_scatter + AudioParam.enable_fft = ARGS.send_fft + TNC.enable_fsk = ARGS.enable_fsk + TNC.low_bandwidth_mode = ARGS.low_bandwidth_mode + ModemParam.tuning_range_fmin = ARGS.tuning_range_fmin + ModemParam.tuning_range_fmax = ARGS.tuning_range_fmax + AudioParam.tx_audio_level = ARGS.tx_audio_level + TNC.respond_to_cq = ARGS.enable_respond_to_cq + ARQ.rx_buffer_size = ARGS.rx_buffer_size + TNC.enable_explorer = ARGS.enable_explorer + AudioParam.audio_auto_tune = ARGS.enable_audio_auto_tune + TNC.enable_stats = ARGS.enable_stats + AudioParam.audio_enable_tci = ARGS.audio_enable_tci + TCIParam.ip = ARGS.tci_ip + TCIParam.port = ARGS.tci_port + ModemParam.tx_delay = ARGS.tx_delay except Exception as e: log.error("[DMN] Error reading config file", exception=e) @@ -320,52 +321,52 @@ def signal_handler(sig, frame): # then we are forcing a station ssid = 0 mycallsign = bytes(conf.get('STATION', 'mycall', 'AA0AA'), "utf-8") mycallsign = helpers.callsign_to_bytes(mycallsign) - static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign) - static.MYCALLSIGN_CRC = helpers.get_crc_24(static.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 - static.SSID_LIST = json.loads(conf.get('STATION', 'ssid_list', '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]')) + Station.ssid_list = json.loads(conf.get('STATION', 'ssid_list', '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]')) - static.MYGRID = bytes(conf.get('STATION', 'mygrid', 'JN12aa'), "utf-8") + Station.mygrid = bytes(conf.get('STATION', 'mygrid', 'JN12aa'), "utf-8") # check if we have an int or str as device name try: - static.AUDIO_INPUT_DEVICE = int(conf.get('AUDIO', 'rx', '0')) + AudioParam.audio_input_device = int(conf.get('AUDIO', 'rx', '0')) except ValueError: - static.AUDIO_INPUT_DEVICE = conf.get('AUDIO', 'rx', '0') + AudioParam.audio_input_device = conf.get('AUDIO', 'rx', '0') try: - static.AUDIO_OUTPUT_DEVICE = int(conf.get('AUDIO', 'tx', '0')) + AudioParam.audio_output_device = int(conf.get('AUDIO', 'tx', '0')) except ValueError: - static.AUDIO_OUTPUT_DEVICE = conf.get('AUDIO', 'tx', '0') - - static.PORT = int(conf.get('NETWORK', 'tncport', '3000')) - static.HAMLIB_RADIOCONTROL = conf.get('RADIO', 'radiocontrol', 'rigctld') - static.HAMLIB_RIGCTLD_IP = conf.get('RADIO', 'rigctld_ip', '127.0.0.1') - static.HAMLIB_RIGCTLD_PORT = str(conf.get('RADIO', 'rigctld_port', '4532')) - static.ENABLE_SCATTER = conf.get('TNC', 'scatter', 'True') - static.ENABLE_FFT = conf.get('TNC', 'fft', 'True') - static.ENABLE_FSK = conf.get('TNC', 'fsk', 'False') - static.LOW_BANDWIDTH_MODE = conf.get('TNC', 'narrowband', 'False') - static.TUNING_RANGE_FMIN = float(conf.get('TNC', 'fmin', '-50.0')) - static.TUNING_RANGE_FMAX = float(conf.get('TNC', 'fmax', '50.0')) - static.TX_AUDIO_LEVEL = int(conf.get('AUDIO', 'txaudiolevel', '100')) - static.RESPOND_TO_CQ = conf.get('TNC', 'qrv', 'True') - static.RX_BUFFER_SIZE = int(conf.get('TNC', 'rxbuffersize', '16')) - static.ENABLE_EXPLORER = conf.get('TNC', 'explorer', 'False') - static.AUDIO_AUTO_TUNE = conf.get('AUDIO', 'auto_tune', 'False') - static.ENABLE_STATS = conf.get('TNC', 'stats', 'False') - static.AUDIO_ENABLE_TCI = conf.get('AUDIO', 'enable_tci', 'False') - static.TCI_IP = str(conf.get('AUDIO', 'tci_ip', 'localhost')) - static.TCI_PORT = int(conf.get('AUDIO', 'tci_port', '50001')) - static.TX_DELAY = int(conf.get('TNC', 'tx_delay', '0')) + AudioParam.audio_output_device = conf.get('AUDIO', 'tx', '0') + + TNC.port = int(conf.get('NETWORK', 'tncport', '3000')) + HamlibParam.hamlib_radiocontrol = conf.get('RADIO', 'radiocontrol', 'rigctld') + 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('TNC', 'scatter', 'True') + AudioParam.enable_fft = conf.get('TNC', 'fft', 'True') + TNC.enable_fsk = conf.get('TNC', 'fsk', 'False') + TNC.low_bandwidth_mode = conf.get('TNC', 'narrowband', 'False') + ModemParam.tuning_range_fmin = float(conf.get('TNC', 'fmin', '-50.0')) + ModemParam.tuning_range_fmax = float(conf.get('TNC', 'fmax', '50.0')) + AudioParam.tx_audio_level = int(conf.get('AUDIO', 'txaudiolevel', '100')) + TNC.respond_to_cq = conf.get('TNC', 'qrv', 'True') + ARQ.rx_buffer_size = int(conf.get('TNC', 'rxbuffersize', '16')) + TNC.enable_explorer = conf.get('TNC', 'explorer', 'False') + AudioParam.audio_auto_tune = conf.get('AUDIO', 'auto_tune', 'False') + TNC.enable_stats = conf.get('TNC', 'stats', 'False') + AudioParam.audio_enable_tci = conf.get('AUDIO', 'enable_tci', 'False') + TCIParam.ip = str(conf.get('AUDIO', 'tci_ip', 'localhost')) + TCIParam.port = int(conf.get('AUDIO', 'tci_port', '50001')) + ModemParam.tx_delay = int(conf.get('TNC', 'tx_delay', '0')) 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(static.MYCALLSIGN.split(b'-')[1]) - if my_ssid not in static.SSID_LIST: - static.SSID_LIST.append(my_ssid) + 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 sock @@ -394,7 +395,7 @@ def signal_handler(sig, frame): log.error("[DMN] logger init error", exception=err) log.info( - "[TNC] Starting FreeDATA", author="DJ2LS", version=static.VERSION + "[TNC] Starting FreeDATA", author="DJ2LS", version=TNC.version ) # start data handler @@ -404,17 +405,17 @@ def signal_handler(sig, frame): modem = modem.RF() # optionally start explorer module - if static.ENABLE_EXPLORER: - log.info("[EXPLORER] Publishing to https://explorer.freedata.app", state=static.ENABLE_EXPLORER) + if TNC.enable_explorer: + log.info("[EXPLORER] Publishing to https://explorer.freedata.app", state=TNC.enable_explorer) explorer = explorer.explorer() # --------------------------------------------START CMD SERVER try: - log.info("[TNC] Starting TCP/IP socket", port=static.PORT) + log.info("[TNC] Starting TCP/IP socket", port=TNC.port) # https://stackoverflow.com/a/16641793 socketserver.TCPServer.allow_reuse_address = True cmdserver = sock.ThreadedTCPServer( - (static.HOST, static.PORT), sock.ThreadedTCPRequestHandler + (TNC.host, TNC.port), sock.ThreadedTCPRequestHandler ) server_thread = threading.Thread(target=cmdserver.serve_forever) @@ -422,7 +423,7 @@ def signal_handler(sig, frame): server_thread.start() except Exception as err: - log.error("[TNC] Starting TCP/IP socket failed", port=static.PORT, e=err) + log.error("[TNC] Starting TCP/IP socket failed", port=TNC.port, e=err) sys.exit(1) while True: - threading.Event().wait(1) + threading.Event().wait(1) \ No newline at end of file diff --git a/tnc/modem.py b/tnc/modem.py index 996bcd1b1..e74054d75 100644 --- a/tnc/modem.py +++ b/tnc/modem.py @@ -23,6 +23,8 @@ import sock import sounddevice as sd import static +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC +from static import FRAME_TYPE import structlog import ujson as json import tci @@ -33,19 +35,24 @@ RXCHANNEL = "" TXCHANNEL = "" -static.TRANSMITTING = False +TNC.transmitting = False # Receive only specific modes to reduce CPU load RECEIVE_SIG0 = True RECEIVE_SIG1 = False RECEIVE_DATAC1 = False RECEIVE_DATAC3 = False +RECEIVE_DATAC4 = False + # state buffer -SIG0_DATAC0_STATE = [] -SIG1_DATAC0_STATE = [] + +SIG0_DATAC13_STATE = [] +SIG1_DATAC13_STATE = [] DAT0_DATAC1_STATE = [] DAT0_DATAC3_STATE = [] +DAT0_DATAC4_STATE = [] + FSK_LDPC0_STATE = [] FSK_LDPC1_STATE = [] @@ -66,7 +73,7 @@ def __init__(self) -> None: self.AUDIO_FRAMES_PER_BUFFER_RX = 2400 * 2 # 8192 # 8192 Let's do some tests with very small chunks for TX - self.AUDIO_FRAMES_PER_BUFFER_TX = 1200 if static.AUDIO_ENABLE_TCI else 2400 * 2 + self.AUDIO_FRAMES_PER_BUFFER_TX = 1200 if AudioParam.audio_enable_tci else 2400 * 2 # 8 * (self.AUDIO_SAMPLE_RATE_RX/self.MODEM_SAMPLE_RATE) == 48 self.AUDIO_CHANNELS = 1 self.MODE = 0 @@ -79,9 +86,7 @@ def __init__(self) -> None: # Make sure our resampler will work assert (self.AUDIO_SAMPLE_RATE_RX / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 # type: ignore - # Small hack for initializing codec2 via codec2.py module - # TODO: Need to change the entire modem module to integrate codec2 module - self.c_lib = codec2.api + # init codec2 resampler self.resampler = codec2.resampler() self.modem_transmit_queue = MODEM_TRANSMIT_QUEUE @@ -98,23 +103,23 @@ def __init__(self) -> None: # Open codec2 instances - # DATAC0 + # DATAC13 # SIGNALLING MODE 0 - Used for Connecting - Payload 14 Bytes - self.sig0_datac0_freedv, \ - self.sig0_datac0_bytes_per_frame, \ - self.sig0_datac0_bytes_out, \ - self.sig0_datac0_buffer, \ - self.sig0_datac0_nin = \ - self.init_codec2_mode(codec2.api.FREEDV_MODE_DATAC0, None) - - # DATAC0 + self.sig0_datac13_freedv, \ + self.sig0_datac13_bytes_per_frame, \ + self.sig0_datac13_bytes_out, \ + self.sig0_datac13_buffer, \ + self.sig0_datac13_nin = \ + self.init_codec2_mode(codec2.FREEDV_MODE.datac13.value, None) + + # DATAC13 # SIGNALLING MODE 1 - Used for ACK/NACK - Payload 5 Bytes - self.sig1_datac0_freedv, \ - self.sig1_datac0_bytes_per_frame, \ - self.sig1_datac0_bytes_out, \ - self.sig1_datac0_buffer, \ - self.sig1_datac0_nin = \ - self.init_codec2_mode(codec2.api.FREEDV_MODE_DATAC0, None) + self.sig1_datac13_freedv, \ + self.sig1_datac13_bytes_per_frame, \ + self.sig1_datac13_bytes_out, \ + self.sig1_datac13_buffer, \ + self.sig1_datac13_nin = \ + self.init_codec2_mode(codec2.FREEDV_MODE.datac13.value, None) # DATAC1 self.dat0_datac1_freedv, \ @@ -122,7 +127,7 @@ def __init__(self) -> None: self.dat0_datac1_bytes_out, \ self.dat0_datac1_buffer, \ self.dat0_datac1_nin = \ - self.init_codec2_mode(codec2.api.FREEDV_MODE_DATAC1, None) + self.init_codec2_mode(codec2.FREEDV_MODE.datac1.value, None) # DATAC3 self.dat0_datac3_freedv, \ @@ -130,7 +135,16 @@ def __init__(self) -> None: self.dat0_datac3_bytes_out, \ self.dat0_datac3_buffer, \ self.dat0_datac3_nin = \ - self.init_codec2_mode(codec2.api.FREEDV_MODE_DATAC3, None) + self.init_codec2_mode(codec2.FREEDV_MODE.datac3.value, None) + + # DATAC4 + self.dat0_datac4_freedv, \ + self.dat0_datac4_bytes_per_frame, \ + self.dat0_datac4_bytes_out, \ + self.dat0_datac4_buffer, \ + self.dat0_datac4_nin = \ + self.init_codec2_mode(codec2.FREEDV_MODE.datac4.value, None) + # FSK LDPC - 0 self.fsk_ldpc_freedv_0, \ @@ -139,7 +153,7 @@ def __init__(self) -> None: self.fsk_ldpc_buffer_0, \ self.fsk_ldpc_nin_0 = \ self.init_codec2_mode( - codec2.api.FREEDV_MODE_FSK_LDPC, + codec2.FREEDV_MODE.fsk_ldpc.value, codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV ) @@ -150,24 +164,27 @@ def __init__(self) -> None: self.fsk_ldpc_buffer_1, \ self.fsk_ldpc_nin_1 = \ self.init_codec2_mode( - codec2.api.FREEDV_MODE_FSK_LDPC, + codec2.FREEDV_MODE.fsk_ldpc.value, codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV ) - # INIT TX MODES - self.freedv_datac0_tx = open_codec2_instance(14) - self.freedv_datac1_tx = open_codec2_instance(10) - self.freedv_datac3_tx = open_codec2_instance(12) - self.freedv_ldpc0_tx = open_codec2_instance(200) - self.freedv_ldpc1_tx = open_codec2_instance(201) - # --------------------------------------------CREATE PYAUDIO INSTANCE - if not TESTMODE and not static.AUDIO_ENABLE_TCI: + # INIT TX MODES - here we need all modes. + self.freedv_datac0_tx = open_codec2_instance(codec2.FREEDV_MODE.datac0.value) + self.freedv_datac1_tx = open_codec2_instance(codec2.FREEDV_MODE.datac1.value) + self.freedv_datac3_tx = open_codec2_instance(codec2.FREEDV_MODE.datac3.value) + self.freedv_datac4_tx = open_codec2_instance(codec2.FREEDV_MODE.datac4.value) + self.freedv_datac13_tx = open_codec2_instance(codec2.FREEDV_MODE.datac13.value) + self.freedv_ldpc0_tx = open_codec2_instance(codec2.FREEDV_MODE.fsk_ldpc_0.value) + self.freedv_ldpc1_tx = open_codec2_instance(codec2.FREEDV_MODE.fsk_ldpc_1.value) + + # --------------------------------------------CREATE PORTAUDIO INSTANCE + if not TESTMODE and not AudioParam.audio_enable_tci: try: self.stream = sd.RawStream( channels=1, dtype="int16", callback=self.callback, - device=(static.AUDIO_INPUT_DEVICE, static.AUDIO_OUTPUT_DEVICE), + device=(AudioParam.audio_input_device, AudioParam.audio_output_device), samplerate=self.AUDIO_SAMPLE_RATE_RX, blocksize=4800, ) @@ -179,7 +196,7 @@ def __init__(self) -> None: try: self.log.debug("[MDM] init: starting pyaudio callback") - # self.audio_stream.start_stream() + # self.audio_stream.start_stream( self.stream.start() except Exception as err: self.log.error("[MDM] init: starting pyaudio callback failed", e=err) @@ -187,7 +204,7 @@ def __init__(self) -> None: elif not TESTMODE: # placeholder area for processing audio via TCI # https://github.com/maksimus1210/TCI - self.log.warning("[MDM] [TCI] Not yet fully implemented", ip=static.TCI_IP, port=static.TCI_PORT) + self.log.warning("[MDM] [TCI] Not yet fully implemented", ip=TCIParam.tci_ip, port=TCIParam.tci_port) # we are trying this by simulating an audio stream Object like with mkfifo class Object: @@ -197,7 +214,7 @@ class Object: self.stream = Object() # lets init TCI module - self.tci_module = tci.TCI() + self.tci_module = tci.TCICtrl() tci_rx_callback_thread = threading.Thread( target=self.tci_rx_callback, @@ -247,35 +264,28 @@ class Object: # --------------------------------------------INIT AND OPEN HAMLIB # Check how we want to control the radio - # TODO: deprecated feature - we can remove this possibly - if static.HAMLIB_RADIOCONTROL == "direct": - print("direct hamlib support deprecated - not usable anymore") - sys.exit(1) - elif static.HAMLIB_RADIOCONTROL == "rigctl": - print("rigctl support deprecated - not usable anymore") - sys.exit(1) - elif static.HAMLIB_RADIOCONTROL == "rigctld": + if HamlibParam.hamlib_radiocontrol == "rigctld": import rigctld as rig - elif static.AUDIO_ENABLE_TCI: + elif AudioParam.audio_enable_tci: self.radio = self.tci_module else: import rigdummy as rig - if not static.AUDIO_ENABLE_TCI: + if not AudioParam.audio_enable_tci: self.radio = rig.radio() self.radio.open_rig( - rigctld_ip=static.HAMLIB_RIGCTLD_IP, - rigctld_port=static.HAMLIB_RIGCTLD_PORT, + rigctld_ip=HamlibParam.hamlib_rigctld_ip, + rigctld_port=HamlibParam.hamlib_rigctld_port, ) # --------------------------------------------START DECODER THREAD - if static.ENABLE_FFT: + if AudioParam.enable_fft: fft_thread = threading.Thread( target=self.calculate_fft, name="FFT_THREAD", daemon=True ) fft_thread.start() - if static.ENABLE_FSK: + if TNC.enable_fsk: audio_thread_fsk_ldpc0 = threading.Thread( target=self.audio_fsk_ldpc_0, name="AUDIO_THREAD FSK LDPC0", daemon=True ) @@ -287,15 +297,15 @@ class Object: audio_thread_fsk_ldpc1.start() else: - audio_thread_sig0_datac0 = threading.Thread( - target=self.audio_sig0_datac0, name="AUDIO_THREAD DATAC0 - 0", daemon=True + audio_thread_sig0_datac13 = threading.Thread( + target=self.audio_sig0_datac13, name="AUDIO_THREAD DATAC13 - 0", daemon=True ) - audio_thread_sig0_datac0.start() + audio_thread_sig0_datac13.start() - audio_thread_sig1_datac0 = threading.Thread( - target=self.audio_sig1_datac0, name="AUDIO_THREAD DATAC0 - 1", daemon=True + audio_thread_sig1_datac13 = threading.Thread( + target=self.audio_sig1_datac13, name="AUDIO_THREAD DATAC13 - 1", daemon=True ) - audio_thread_sig1_datac0.start() + audio_thread_sig1_datac13.start() audio_thread_dat0_datac1 = threading.Thread( target=self.audio_dat0_datac1, name="AUDIO_THREAD DATAC1", daemon=True @@ -307,6 +317,11 @@ class Object: ) audio_thread_dat0_datac3.start() + audio_thread_dat0_datac4 = threading.Thread( + target=self.audio_dat0_datac4, name="AUDIO_THREAD DATAC4", daemon=True + ) + audio_thread_dat0_datac4.start() + hamlib_thread = threading.Thread( target=self.update_rig_data, name="HAMLIB_THREAD", daemon=True ) @@ -337,7 +352,7 @@ def tci_tx_callback(self) -> None: threading.Event().wait(0.01) if len(self.modoutqueue) > 0 and not self.mod_out_locked: - static.PTT_STATE = self.radio.set_ptt(True) + HamlibParam.ptt_state = self.radio.set_ptt(True) jsondata = {"ptt": "True"} data_out = json.dumps(jsondata) sock.SOCKET_QUEUE.put(data_out) @@ -364,12 +379,13 @@ def tci_rx_callback(self) -> None: length_x = len(x) for data_buffer, receive in [ - (self.sig0_datac0_buffer, RECEIVE_SIG0), - (self.sig1_datac0_buffer, RECEIVE_SIG1), + (self.sig0_datac13_buffer, RECEIVE_SIG0), + (self.sig1_datac13_buffer, RECEIVE_SIG1), (self.dat0_datac1_buffer, RECEIVE_DATAC1), (self.dat0_datac3_buffer, RECEIVE_DATAC3), - (self.fsk_ldpc_buffer_0, static.ENABLE_FSK), - (self.fsk_ldpc_buffer_1, static.ENABLE_FSK), + (self.dat0_datac4_buffer, RECEIVE_DATAC4), + (self.fsk_ldpc_buffer_0, TNC.enable_fsk), + (self.fsk_ldpc_buffer_1, TNC.enable_fsk), ]: if ( not (data_buffer.nbuffer + length_x) > data_buffer.size @@ -397,12 +413,13 @@ def mkfifo_read_callback(self) -> None: length_x = len(x) for data_buffer, receive in [ - (self.sig0_datac0_buffer, RECEIVE_SIG0), - (self.sig1_datac0_buffer, RECEIVE_SIG1), + (self.sig0_datac13_buffer, RECEIVE_SIG0), + (self.sig1_datac13_buffer, RECEIVE_SIG1), (self.dat0_datac1_buffer, RECEIVE_DATAC1), (self.dat0_datac3_buffer, RECEIVE_DATAC3), - (self.fsk_ldpc_buffer_0, static.ENABLE_FSK), - (self.fsk_ldpc_buffer_1, static.ENABLE_FSK), + (self.dat0_datac4_buffer, RECEIVE_DATAC4), + (self.fsk_ldpc_buffer_0, TNC.enable_fsk), + (self.fsk_ldpc_buffer_1, TNC.enable_fsk), ]: if ( not (data_buffer.nbuffer + length_x) > data_buffer.size @@ -443,38 +460,39 @@ def callback(self, data_in48k, outdata, frames, time, status) -> None: x = self.resampler.resample48_to_8(x) # audio recording for debugging purposes - if static.AUDIO_RECORD: - # static.AUDIO_RECORD_FILE.write(x) - static.AUDIO_RECORD_FILE.writeframes(x) + if AudioParam.audio_record: + # AudioParam.audio_record_file.write(x) + AudioParam.audio_record_file.writeframes(x) # Avoid decoding when transmitting to reduce CPU # TODO: Overriding this for testing purposes - # if not static.TRANSMITTING: + # if not TNC.transmitting: length_x = len(x) # Avoid buffer overflow by filling only if buffer for # selected datachannel mode is not full for audiobuffer, receive, index in [ - (self.sig0_datac0_buffer, RECEIVE_SIG0, 0), - (self.sig1_datac0_buffer, RECEIVE_SIG1, 1), + (self.sig0_datac13_buffer, RECEIVE_SIG0, 0), + (self.sig1_datac13_buffer, RECEIVE_SIG1, 1), (self.dat0_datac1_buffer, RECEIVE_DATAC1, 2), (self.dat0_datac3_buffer, RECEIVE_DATAC3, 3), - (self.fsk_ldpc_buffer_0, static.ENABLE_FSK, 4), - (self.fsk_ldpc_buffer_1, static.ENABLE_FSK, 5), + (self.dat0_datac4_buffer, RECEIVE_DATAC4, 4), + (self.fsk_ldpc_buffer_0, TNC.enable_fsk, 5), + (self.fsk_ldpc_buffer_1, TNC.enable_fsk, 6), ]: if (audiobuffer.nbuffer + length_x) > audiobuffer.size: - static.BUFFER_OVERFLOW_COUNTER[index] += 1 + AudioParam.buffer_overflow_counter[index] += 1 elif receive: audiobuffer.push(x) - # end of "not static.TRANSMITTING" if block + # end of "not TNC.transmitting" if block if not self.modoutqueue or self.mod_out_locked: data_out48k = np.zeros(frames, dtype=np.int16) self.fft_data = x else: - if not static.PTT_STATE: + if not HamlibParam.ptt_state: # TODO: Moved to this place for testing # Maybe we can avoid moments of silence before transmitting - static.PTT_STATE = self.radio.set_ptt(True) + HamlibParam.ptt_state = self.radio.set_ptt(True) jsondata = {"ptt": "True"} data_out = json.dumps(jsondata) sock.SOCKET_QUEUE.put(data_out) @@ -502,38 +520,33 @@ def transmit( frames: """ + self.reset_data_sync() - """ - sig0 = 14 - sig1 = 14 - datac0 = 14 - datac1 = 10 - datac3 = 12 - fsk_ldpc = 9 - fsk_ldpc_0 = 200 - fsk_ldpc_1 = 201 - """ - if mode == 14: + if mode == codec2.FREEDV_MODE.datac0.value: freedv = self.freedv_datac0_tx - elif mode == 10: + elif mode == codec2.FREEDV_MODE.datac1.value: freedv = self.freedv_datac1_tx - elif mode == 12: + elif mode == codec2.FREEDV_MODE.datac3.value: freedv = self.freedv_datac3_tx - elif mode == 200: + elif mode == codec2.FREEDV_MODE.datac4.value: + freedv = self.freedv_datac4_tx + elif mode == codec2.FREEDV_MODE.datac13.value: + freedv = self.freedv_datac13_tx + elif mode == codec2.FREEDV_MODE.fsk_ldpc_0.value: freedv = self.freedv_ldpc0_tx - elif mode == 201: + elif mode == codec2.FREEDV_MODE.fsk_ldpc_1.value: freedv = self.freedv_ldpc1_tx else: return False - static.TRANSMITTING = True + TNC.transmitting = True # if we're transmitting FreeDATA signals, reset channel busy state - static.CHANNEL_BUSY = False + ModemParam.channel_busy = False start_of_transmission = time.time() # TODO: Moved ptt toggle some steps before audio is ready for testing # Toggle ptt early to save some time and send ptt state via socket - # static.PTT_STATE = self.radio.set_ptt(True) + # HamlibParam.ptt_state = self.radio.set_ptt(True) # jsondata = {"ptt": "True"} # data_out = json.dumps(jsondata) # sock.SOCKET_QUEUE.put(data_out) @@ -564,30 +577,32 @@ def transmit( ) # Add empty data to handle ptt toggle time - if static.TX_DELAY > 0: - data_delay = int(self.MODEM_SAMPLE_RATE * (static.TX_DELAY / 1000)) # type: ignore + if ModemParam.tx_delay > 0: + data_delay = int(self.MODEM_SAMPLE_RATE * (ModemParam.tx_delay / 1000)) # type: ignore mod_out_silence = ctypes.create_string_buffer(data_delay * 2) txbuffer = bytes(mod_out_silence) else: txbuffer = bytes() self.log.debug( - "[MDM] TRANSMIT", mode=self.MODE, payload=payload_bytes_per_frame, delay=static.TX_DELAY + "[MDM] TRANSMIT", mode=self.MODE, payload=payload_bytes_per_frame, delay=ModemParam.tx_delay ) for _ in range(repeats): - # codec2 fsk preamble may be broken - - # at least it sounds like that, so we are disabling it for testing - if self.MODE not in [ - codec2.FREEDV_MODE.fsk_ldpc_0.value, - codec2.FREEDV_MODE.fsk_ldpc_1.value, - ]: - # Write preamble to txbuffer - codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble) - txbuffer += bytes(mod_out_preamble) - # Create modulaton for all frames in the list + # Create modulation for all frames in the list for frame in frames: + # Write preamble to txbuffer + # codec2 fsk preamble may be broken - + # at least it sounds like that, so we are disabling it for testing + if self.MODE not in [ + codec2.FREEDV_MODE.fsk_ldpc_0.value, + codec2.FREEDV_MODE.fsk_ldpc_1.value, + ]: + # Write preamble to txbuffer + codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble) + txbuffer += bytes(mod_out_preamble) + # Create buffer for data # Use this if CRC16 checksum is required (DATAc1-3) buffer = bytearray(payload_bytes_per_frame) @@ -611,16 +626,16 @@ def transmit( codec2.api.freedv_rawdatatx(freedv, mod_out, data) txbuffer += bytes(mod_out) - # codec2 fsk postamble may be broken - - # at least it sounds like that, so we are disabling it for testing - if self.MODE not in [ - codec2.FREEDV_MODE.fsk_ldpc_0.value, - codec2.FREEDV_MODE.fsk_ldpc_1.value, - ]: - # Write postamble to txbuffer - codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble) - # Append postamble to txbuffer - txbuffer += bytes(mod_out_postamble) + # codec2 fsk postamble may be broken - + # at least it sounds like that, so we are disabling it for testing + if self.MODE not in [ + codec2.FREEDV_MODE.fsk_ldpc_0.value, + codec2.FREEDV_MODE.fsk_ldpc_1.value, + ]: + # Write postamble to txbuffer + codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble) + # Append postamble to txbuffer + txbuffer += bytes(mod_out_postamble) # Add delay to end of frames samples_delay = int(self.MODEM_SAMPLE_RATE * (repeat_delay / 1000)) # type: ignore @@ -631,35 +646,35 @@ def transmit( x = np.frombuffer(txbuffer, dtype=np.int16) # enable / disable AUDIO TUNE Feature / ALC correction - if static.AUDIO_AUTO_TUNE: - if static.HAMLIB_ALC == 0.0: - static.TX_AUDIO_LEVEL = static.TX_AUDIO_LEVEL + 20 - elif 0.0 < static.HAMLIB_ALC <= 0.1: - print("0.0 < static.HAMLIB_ALC <= 0.1") - static.TX_AUDIO_LEVEL = static.TX_AUDIO_LEVEL + 2 - self.log.debug("[MDM] AUDIO TUNE", audio_level=str(static.TX_AUDIO_LEVEL), - alc_level=str(static.HAMLIB_ALC)) - elif 0.1 < static.HAMLIB_ALC < 0.2: - print("0.1 < static.HAMLIB_ALC < 0.2") - static.TX_AUDIO_LEVEL = static.TX_AUDIO_LEVEL - self.log.debug("[MDM] AUDIO TUNE", audio_level=str(static.TX_AUDIO_LEVEL), - alc_level=str(static.HAMLIB_ALC)) - elif 0.2 < static.HAMLIB_ALC < 0.99: - print("0.2 < static.HAMLIB_ALC < 0.99") - static.TX_AUDIO_LEVEL = static.TX_AUDIO_LEVEL - 20 - self.log.debug("[MDM] AUDIO TUNE", audio_level=str(static.TX_AUDIO_LEVEL), - alc_level=str(static.HAMLIB_ALC)) - elif 1.0 >= static.HAMLIB_ALC: - print("1.0 >= static.HAMLIB_ALC") - static.TX_AUDIO_LEVEL = static.TX_AUDIO_LEVEL - 40 - self.log.debug("[MDM] AUDIO TUNE", audio_level=str(static.TX_AUDIO_LEVEL), - alc_level=str(static.HAMLIB_ALC)) + if AudioParam.audio_auto_tune: + if HamlibParam.alc == 0.0: + AudioParam.tx_audio_level = AudioParam.tx_audio_level + 20 + elif 0.0 < HamlibParam.alc <= 0.1: + print("0.0 < HamlibParam.alc <= 0.1") + AudioParam.tx_audio_level = AudioParam.tx_audio_level + 2 + self.log.debug("[MDM] AUDIO TUNE", audio_level=str(AudioParam.tx_audio_level), + alc_level=str(HamlibParam.alc)) + elif 0.1 < HamlibParam.alc < 0.2: + print("0.1 < HamlibParam.alc < 0.2") + AudioParam.tx_audio_level = AudioParam.tx_audio_level + self.log.debug("[MDM] AUDIO TUNE", audio_level=str(AudioParam.tx_audio_level), + alc_level=str(HamlibParam.alc)) + elif 0.2 < HamlibParam.alc < 0.99: + print("0.2 < HamlibParam.alc < 0.99") + AudioParam.tx_audio_level = AudioParam.tx_audio_level - 20 + self.log.debug("[MDM] AUDIO TUNE", audio_level=str(AudioParam.tx_audio_level), + alc_level=str(HamlibParam.alc)) + elif 1.0 >= HamlibParam.alc: + print("1.0 >= HamlibParam.alc") + AudioParam.tx_audio_level = AudioParam.tx_audio_level - 40 + self.log.debug("[MDM] AUDIO TUNE", audio_level=str(AudioParam.tx_audio_level), + alc_level=str(HamlibParam.alc)) else: - self.log.debug("[MDM] AUDIO TUNE", audio_level=str(static.TX_AUDIO_LEVEL), - alc_level=str(static.HAMLIB_ALC)) - x = set_audio_volume(x, static.TX_AUDIO_LEVEL) + self.log.debug("[MDM] AUDIO TUNE", audio_level=str(AudioParam.tx_audio_level), + alc_level=str(HamlibParam.alc)) + x = set_audio_volume(x, AudioParam.tx_audio_level) - if not static.AUDIO_ENABLE_TCI: + if not AudioParam.audio_enable_tci: txbuffer_out = self.resampler.resample8_to_48(x) else: txbuffer_out = x @@ -689,7 +704,7 @@ def transmit( self.mod_out_locked = False # we need to wait manually for tci processing - if static.AUDIO_ENABLE_TCI: + if AudioParam.audio_enable_tci: duration = len(txbuffer_out) / 8000 timestamp_to_sleep = time.time() + duration self.log.debug("[MDM] TCI calculated duration", duration=duration) @@ -702,7 +717,7 @@ def transmit( tci_timeout_reached = True while self.modoutqueue or not tci_timeout_reached: - if static.AUDIO_ENABLE_TCI: + if AudioParam.audio_enable_tci: if time.time() < timestamp_to_sleep: tci_timeout_reached = False else: @@ -710,9 +725,9 @@ def transmit( threading.Event().wait(0.01) # if we're transmitting FreeDATA signals, reset channel busy state - static.CHANNEL_BUSY = False + ModemParam.channel_busy = False - static.PTT_STATE = self.radio.set_ptt(False) + HamlibParam.ptt_state = self.radio.set_ptt(False) # Push ptt state to socket stream jsondata = {"ptt": "False"} @@ -723,7 +738,7 @@ def transmit( self.mod_out_locked = True self.modem_transmit_queue.task_done() - static.TRANSMITTING = False + TNC.transmitting = False threading.Event().set() end_of_transmission = time.time() @@ -781,14 +796,14 @@ def demodulate_audio( if rx_status != 0: # we need to disable this if in testmode as its causing problems with FIFO it seems if not TESTMODE: - static.IS_CODEC2_TRAFFIC = True + ModemParam.is_codec2_traffic = True self.log.debug( "[MDM] [demod_audio] modem state", mode=mode_name, rx_status=rx_status, sync_flag=codec2.api.rx_sync_flags_to_text[rx_status] ) else: - static.IS_CODEC2_TRAFFIC = False + ModemParam.is_codec2_traffic = False if rx_status == 10: state_buffer.append(rx_status) @@ -796,19 +811,35 @@ def demodulate_audio( audiobuffer.pop(nin) nin = codec2.api.freedv_nin(freedv) if nbytes == bytes_per_frame: - # process commands only if static.LISTEN = True - if static.LISTEN: - self.log.debug( - "[MDM] [demod_audio] Pushing received data to received_queue", nbytes=nbytes - ) - self.modem_received_queue.put([bytes_out, freedv, bytes_per_frame]) - self.get_scatter(freedv) - self.calculate_snr(freedv) - state_buffer = [] + + # process commands only if TNC.listen = True + if TNC.listen: + + + # ignore data channel opener frames for avoiding toggle states + # use case: opener already received, but ack got lost and we are receiving + # an opener again + if mode_name in ["sig1-datac13"] and int.from_bytes(bytes(bytes_out[:1]), "big") in [ + FRAME_TYPE.ARQ_SESSION_OPEN.value, + FRAME_TYPE.ARQ_DC_OPEN_W.value, + FRAME_TYPE.ARQ_DC_OPEN_ACK_W.value, + FRAME_TYPE.ARQ_DC_OPEN_N.value, + FRAME_TYPE.ARQ_DC_OPEN_ACK_N.value + ]: + print("dropp") + else: + self.log.debug( + "[MDM] [demod_audio] Pushing received data to received_queue", nbytes=nbytes + ) + + self.modem_received_queue.put([bytes_out, freedv, bytes_per_frame]) + self.get_scatter(freedv) + self.calculate_snr(freedv) + state_buffer = [] else: self.log.warning( "[MDM] [demod_audio] received frame but ignored processing", - listen=static.LISTEN + listen=TNC.listen ) except Exception as e: self.log.warning("[MDM] [demod_audio] Stream not active anymore", e=e) @@ -830,7 +861,7 @@ def init_codec2_mode(self, mode, adv): # FSK Long-distance Parity Code 1 - data frames c2instance = ctypes.cast( codec2.api.freedv_open_advanced( - codec2.api.FREEDV_MODE_FSK_LDPC, + codec2.FREEDV_MODE.fsk_ldpc.value, ctypes.byref(adv), ), ctypes.c_void_p, @@ -843,10 +874,10 @@ def init_codec2_mode(self, mode, adv): ) # set tuning range - self.c_lib.freedv_set_tuning_range( + codec2.api.freedv_set_tuning_range( c2instance, - ctypes.c_float(static.TUNING_RANGE_FMIN), - ctypes.c_float(static.TUNING_RANGE_FMAX), + ctypes.c_float(ModemParam.tuning_range_fmin), + ctypes.c_float(ModemParam.tuning_range_fmax), ) # get bytes per frame @@ -868,44 +899,56 @@ def init_codec2_mode(self, mode, adv): # Additional Datac0-specific information - these are not referenced anywhere else. # self.sig0_datac0_payload_per_frame = self.sig0_datac0_bytes_per_frame - 2 - # self.sig0_datac0_n_nom_modem_samples = self.c_lib.freedv_get_n_nom_modem_samples( + # self.sig0_datac0_n_nom_modem_samples = codec2.api.freedv_get_n_nom_modem_samples( # self.sig0_datac0_freedv # ) - # self.sig0_datac0_n_tx_modem_samples = self.c_lib.freedv_get_n_tx_modem_samples( + # self.sig0_datac0_n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples( # self.sig0_datac0_freedv # ) # self.sig0_datac0_n_tx_preamble_modem_samples = ( - # self.c_lib.freedv_get_n_tx_preamble_modem_samples(self.sig0_datac0_freedv) + # codec2.api.freedv_get_n_tx_preamble_modem_samples(self.sig0_datac0_freedv) # ) # self.sig0_datac0_n_tx_postamble_modem_samples = ( - # self.c_lib.freedv_get_n_tx_postamble_modem_samples(self.sig0_datac0_freedv) + # codec2.api.freedv_get_n_tx_postamble_modem_samples(self.sig0_datac0_freedv) # ) # return values return c2instance, bytes_per_frame, bytes_out, audio_buffer, nin - def audio_sig0_datac0(self) -> None: - """Receive data encoded with datac0 - 0""" - self.sig0_datac0_nin = self.demodulate_audio( - self.sig0_datac0_buffer, - self.sig0_datac0_nin, - self.sig0_datac0_freedv, - self.sig0_datac0_bytes_out, - self.sig0_datac0_bytes_per_frame, - SIG0_DATAC0_STATE, - "sig0-datac0" + def audio_sig0_datac13(self) -> None: + """Receive data encoded with datac13 - 0""" + self.sig0_datac13_nin = self.demodulate_audio( + self.sig0_datac13_buffer, + self.sig0_datac13_nin, + self.sig0_datac13_freedv, + self.sig0_datac13_bytes_out, + self.sig0_datac13_bytes_per_frame, + SIG0_DATAC13_STATE, + "sig0-datac13" + ) + + def audio_sig1_datac13(self) -> None: + """Receive data encoded with datac13 - 1""" + self.sig1_datac13_nin = self.demodulate_audio( + self.sig1_datac13_buffer, + self.sig1_datac13_nin, + self.sig1_datac13_freedv, + self.sig1_datac13_bytes_out, + self.sig1_datac13_bytes_per_frame, + SIG1_DATAC13_STATE, + "sig1-datac13" ) - def audio_sig1_datac0(self) -> None: - """Receive data encoded with datac0 - 1""" - self.sig1_datac0_nin = self.demodulate_audio( - self.sig1_datac0_buffer, - self.sig1_datac0_nin, - self.sig1_datac0_freedv, - self.sig1_datac0_bytes_out, - self.sig1_datac0_bytes_per_frame, - SIG1_DATAC0_STATE, - "sig1-datac0" + def audio_dat0_datac4(self) -> None: + """Receive data encoded with datac4""" + self.dat0_datac4_nin = self.demodulate_audio( + self.dat0_datac4_buffer, + self.dat0_datac4_nin, + self.dat0_datac4_freedv, + self.dat0_datac4_bytes_out, + self.dat0_datac4_bytes_per_frame, + DAT0_DATAC4_STATE, + "dat0-datac4" ) def audio_dat0_datac1(self) -> None: @@ -986,7 +1029,7 @@ def worker_received(self) -> None: def get_frequency_offset(self, freedv: ctypes.c_void_p) -> float: """ Ask codec2 for the calculated (audio) frequency offset of the received signal. - Side-effect: sets static.FREQ_OFFSET + Side-effect: sets ModemParam.frequency_offset :param freedv: codec2 instance to query :type freedv: ctypes.c_void_p @@ -994,25 +1037,25 @@ def get_frequency_offset(self, freedv: ctypes.c_void_p) -> float: :rtype: float """ modemStats = codec2.MODEMSTATS() - self.c_lib.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats)) + codec2.api.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats)) offset = round(modemStats.foff) * (-1) - static.FREQ_OFFSET = offset + ModemParam.frequency_offset = offset return offset def get_scatter(self, freedv: ctypes.c_void_p) -> None: """ Ask codec2 for data about the received signal and calculate the scatter plot. - Side-effect: sets static.SCATTER + Side-effect: sets ModemParam.scatter :param freedv: codec2 instance to query :type freedv: ctypes.c_void_p """ - if not static.ENABLE_SCATTER: + if not ModemParam.enable_scatter: return modemStats = codec2.MODEMSTATS() ctypes.cast( - self.c_lib.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats)), + codec2.api.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats)), ctypes.c_void_p, ) @@ -1035,16 +1078,16 @@ def get_scatter(self, freedv: ctypes.c_void_p) -> None: # Send all the data if we have too-few samples, otherwise send a sampling if 150 > len(scatterdata) > 0: - static.SCATTER = scatterdata + ModemParam.scatter = scatterdata else: # only take every tenth data point - static.SCATTER = scatterdata[::10] + ModemParam.scatter = scatterdata[::10] def calculate_snr(self, freedv: ctypes.c_void_p) -> float: """ Ask codec2 for data about the received signal and calculate the signal-to-noise ratio. - Side-effect: sets static.SNR + Side-effect: sets ModemParam.snr :param freedv: codec2 instance to query :type freedv: ctypes.c_void_p @@ -1055,7 +1098,7 @@ def calculate_snr(self, freedv: ctypes.c_void_p) -> float: modem_stats_snr = ctypes.c_float() modem_stats_sync = ctypes.c_int() - self.c_lib.freedv_get_modem_stats( + codec2.api.freedv_get_modem_stats( freedv, ctypes.byref(modem_stats_sync), ctypes.byref(modem_stats_snr) ) modem_stats_snr = modem_stats_snr.value @@ -1063,15 +1106,15 @@ def calculate_snr(self, freedv: ctypes.c_void_p) -> float: snr = round(modem_stats_snr, 1) self.log.info("[MDM] calculate_snr: ", snr=snr) - static.SNR = snr - # static.SNR = np.clip( + ModemParam.snr = snr + # ModemParam.snr = np.clip( # snr, -127, 127 # ) # limit to max value of -128/128 as a possible fix of #188 - return static.SNR + return ModemParam.snr except Exception as err: self.log.error(f"[MDM] calculate_snr: Exception: {err}") - static.SNR = 0 - return static.SNR + ModemParam.snr = 0 + return ModemParam.snr def set_rig_data(self) -> None: """ @@ -1091,29 +1134,29 @@ def update_rig_data(self) -> None: """ Request information about the current state of the radio via hamlib Side-effect: sets - - static.HAMLIB_FREQUENCY - - static.HAMLIB_MODE - - static.HAMLIB_BANDWIDTH + - HamlibParam.hamlib_frequency + - HamlibParam.hamlib_mode + - HamlibParam.hamlib_bandwidth """ while True: # this looks weird, but is necessary for avoiding rigctld packet colission sock threading.Event().wait(0.25) - static.HAMLIB_FREQUENCY = self.radio.get_frequency() + HamlibParam.hamlib_frequency = self.radio.get_frequency() threading.Event().wait(0.1) - static.HAMLIB_MODE = self.radio.get_mode() + HamlibParam.hamlib_mode = self.radio.get_mode() threading.Event().wait(0.1) - static.HAMLIB_BANDWIDTH = self.radio.get_bandwidth() + HamlibParam.hamlib_bandwidth = self.radio.get_bandwidth() threading.Event().wait(0.1) - static.HAMLIB_STATUS = self.radio.get_status() + HamlibParam.hamlib_status = self.radio.get_status() threading.Event().wait(0.1) - if static.TRANSMITTING: - static.HAMLIB_ALC = self.radio.get_alc() + if TNC.transmitting: + HamlibParam.alc = self.radio.get_alc() threading.Event().wait(0.1) - # static.HAMLIB_RF = self.radio.get_level() + # HamlibParam.hamlib_rf = self.radio.get_level() # threading.Event().wait(0.1) - static.HAMLIB_STRENGTH = self.radio.get_strength() + HamlibParam.hamlib_strength = self.radio.get_strength() - # print(f"ALC: {static.HAMLIB_ALC}, RF: {static.HAMLIB_RF}, STRENGTH: {static.HAMLIB_STRENGTH}") + # print(f"ALC: {HamlibParam.alc}, RF: {HamlibParam.hamlib_rf}, STRENGTH: {HamlibParam.hamlib_strength}") def calculate_fft(self) -> None: """ @@ -1152,7 +1195,7 @@ def calculate_fft(self) -> None: # Therefore we are setting it to 100 so it will be highlighted # Have to do this when we are not transmitting so our # own sending data will not affect this too much - if not static.TRANSMITTING: + if not TNC.transmitting: dfft[dfft > avg + 15] = 100 # Calculate audio dbfs @@ -1162,27 +1205,26 @@ def calculate_fft(self) -> None: if rms_counter > 50: d = np.frombuffer(self.fft_data, np.int16).astype(np.float32) # calculate RMS and then dBFS - # TODO: Need to change static.AUDIO_RMS to AUDIO_DBFS somewhen # https://dsp.stackexchange.com/questions/8785/how-to-compute-dbfs # try except for avoiding runtime errors by division/0 try: rms = int(np.sqrt(np.max(d ** 2))) if rms == 0: raise ZeroDivisionError - static.AUDIO_DBFS = 20 * np.log10(rms / 32768) + AudioParam.audio_dbfs = 20 * np.log10(rms / 32768) except Exception as e: self.log.warning( "[MDM] fft calculation error - please check your audio setup", e=e, ) - static.AUDIO_DBFS = -100 + AudioParam.audio_dbfs = -100 rms_counter = 0 # Convert data to int to decrease size dfft = dfft.astype(int) - # Create list of dfft for later pushing to static.FFT + # Create list of dfft for later pushing to AudioParam.fft dfftlist = dfft.tolist() # Reduce area where the busy detection is enabled @@ -1196,30 +1238,46 @@ def calculate_fft(self) -> None: # 2700Hz = 266 # 3200Hz = 315 - # define the area, we are detecting busy state - dfft = dfft[120:176] if static.LOW_BANDWIDTH_MODE else dfft[65:231] - - # Check for signals higher than average by checking for "100" - # If we have a signal, increment our channel_busy delay counter - # so we have a smoother state toggle - if np.sum(dfft[dfft > avg + 15]) >= 400 and not static.TRANSMITTING: - static.CHANNEL_BUSY = True - # Limit delay counter to a maximum of 200. The higher this value, - # the longer we will wait until releasing state - channel_busy_delay = min(channel_busy_delay + 10, 200) - else: - # Decrement channel busy counter if no signal has been detected. - channel_busy_delay = max(channel_busy_delay - 1, 0) - # When our channel busy counter reaches 0, toggle state to False - if channel_busy_delay == 0: - static.CHANNEL_BUSY = False + # slot + slot = 0 + slot1 = [0, 65] + slot2 = [65,120] + slot3 = [120, 176] + slot4 = [176, 231] + slot5 = [231, len(dfftlist)] + for range in [slot1, slot2, slot3, slot4, slot5]: + + range_start = range[0] + range_end = range[1] + # define the area, we are detecting busy state + #dfft = dfft[120:176] if TNC.low_bandwidth_mode else dfft[65:231] + dfft = dfft[range_start:range_end] + # Check for signals higher than average by checking for "100" + # If we have a signal, increment our channel_busy delay counter + # so we have a smoother state toggle + if np.sum(dfft[dfft > avg + 15]) >= 400 and not TNC.transmitting: + ModemParam.channel_busy = True + ModemParam.channel_busy_slot[slot] = True + # Limit delay counter to a maximum of 200. The higher this value, + # the longer we will wait until releasing state + channel_busy_delay = min(channel_busy_delay + 10, 200) + else: + # Decrement channel busy counter if no signal has been detected. + channel_busy_delay = max(channel_busy_delay - 1, 0) + # When our channel busy counter reaches 0, toggle state to False + if channel_busy_delay == 0: + ModemParam.channel_busy = False + ModemParam.channel_busy_slot[slot] = False + + # increment slot + slot += 1 - static.FFT = dfftlist[:315] # 315 --> bandwidth 3200 + AudioParam.fft = dfftlist[:315] # 315 --> bandwidth 3200 except Exception as err: self.log.error(f"[MDM] calculate_fft: Exception: {err}") self.log.debug("[MDM] Setting fft=0") # else 0 - static.FFT = [0] + AudioParam.fft = [0] def set_frames_per_burst(self, frames_per_burst: int) -> None: """ @@ -1232,10 +1290,26 @@ def set_frames_per_burst(self, frames_per_burst: int) -> None: frames_per_burst = min(frames_per_burst, 1) frames_per_burst = max(frames_per_burst, 5) + frames_per_burst = 1 + codec2.api.freedv_set_frames_per_burst(self.dat0_datac1_freedv, frames_per_burst) codec2.api.freedv_set_frames_per_burst(self.dat0_datac3_freedv, frames_per_burst) + codec2.api.freedv_set_frames_per_burst(self.dat0_datac4_freedv, frames_per_burst) codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, frames_per_burst) + def reset_data_sync(self) -> None: + """ + reset sync state for data modes + + :param frames_per_burst: Number of frames per burst requested + :type frames_per_burst: int + """ + + codec2.api.freedv_set_sync(self.dat0_datac1_freedv, 0) + codec2.api.freedv_set_sync(self.dat0_datac3_freedv, 0) + codec2.api.freedv_set_sync(self.dat0_datac4_freedv, 0) + codec2.api.freedv_set_sync(self.fsk_ldpc_freedv_0, 0) + def open_codec2_instance(mode: int) -> ctypes.c_void_p: """ @@ -1249,7 +1323,7 @@ def open_codec2_instance(mode: int) -> ctypes.c_void_p: if mode in [codec2.FREEDV_MODE.fsk_ldpc_0.value]: return ctypes.cast( codec2.api.freedv_open_advanced( - codec2.api.FREEDV_MODE_FSK_LDPC, + codec2.FREEDV_MODE.fsk_ldpc.value, ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV), ), ctypes.c_void_p, @@ -1258,7 +1332,7 @@ def open_codec2_instance(mode: int) -> ctypes.c_void_p: if mode in [codec2.FREEDV_MODE.fsk_ldpc_1.value]: return ctypes.cast( codec2.api.freedv_open_advanced( - codec2.api.FREEDV_MODE_FSK_LDPC, + codec2.FREEDV_MODE.fsk_ldpc.value, ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV), ), ctypes.c_void_p, @@ -1277,7 +1351,7 @@ def get_bytes_per_frame(mode: int) -> int: :rtype: int """ freedv = open_codec2_instance(mode) - + # TODO: add close session # get number of bytes per frame for mode return int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8) @@ -1320,5 +1394,8 @@ def get_modem_error_state(): if RECEIVE_DATAC3 and 10 in DAT0_DATAC3_STATE: DAT0_DATAC3_STATE.clear() return True + if RECEIVE_DATAC4 and 10 in DAT0_DATAC4_STATE: + DAT0_DATAC4_STATE.clear() + return True - return False \ No newline at end of file + return False diff --git a/tnc/queues.py b/tnc/queues.py index 762113eab..169741516 100644 --- a/tnc/queues.py +++ b/tnc/queues.py @@ -3,6 +3,7 @@ """ import queue import static +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, TCIParam, TNC DATA_QUEUE_TRANSMIT = queue.Queue() DATA_QUEUE_RECEIVED = queue.Queue() @@ -16,7 +17,7 @@ AUDIO_TRANSMIT_QUEUE = queue.Queue() # Initialize FIFO queue to finally store received data -RX_BUFFER = queue.Queue(maxsize=static.RX_BUFFER_SIZE) +RX_BUFFER = queue.Queue(maxsize=ARQ.rx_buffer_size) # Commands we want to send to rigctld RIGCTLD_COMMAND_QUEUE = queue.Queue() \ No newline at end of file diff --git a/tnc/rigctld.py b/tnc/rigctld.py index a6800087d..27bce8555 100644 --- a/tnc/rigctld.py +++ b/tnc/rigctld.py @@ -10,9 +10,7 @@ import structlog import threading import static - -# set global hamlib version -hamlib_version = 0 +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, TCIParam class radio: @@ -243,7 +241,7 @@ def get_alc(self): if 'RPRT' not in alc: try: alc = float(alc) - self.alc = alc if alc != 0.0 else static.HAMLIB_ALC + self.alc = alc if alc != 0.0 else HamlibParam.alc except ValueError: self.alc = 0.0 diff --git a/tnc/selftest.py b/tnc/selftest.py index f4da6462c..5cfec42a7 100644 --- a/tnc/selftest.py +++ b/tnc/selftest.py @@ -7,6 +7,8 @@ # pylint: disable=import-outside-toplevel, attribute-defined-outside-init import sys import structlog +from static import ARQ, Audio, Beacon, Channel, Daemon, Hamlib, Modem, Station, TCI, TNC + log = structlog.get_logger("selftest") diff --git a/tnc/sock.py b/tnc/sock.py index ab93cb3e6..df126bde3 100644 --- a/tnc/sock.py +++ b/tnc/sock.py @@ -27,6 +27,7 @@ import wave import helpers import static +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC import structlog from random import randrange import ujson as json @@ -67,7 +68,7 @@ def send_to_client(self): while self.connection_alive and not CLOSE_SIGNAL: # send tnc state as network stream # check server port against daemon port and send corresponding data - if self.server.server_address[1] == static.PORT and not static.TNCSTARTED: + if self.server.server_address[1] == TNC.port and not Daemon.tncstarted: data = send_tnc_state() if data != tempdata: tempdata = data @@ -98,9 +99,7 @@ def send_to_client(self): self.log.debug("[SCK] catch harmless RuntimeError: Set changed size during iteration", e=err) # we want to transmit scatter data only once to reduce network traffic - static.SCATTER = [] - # we want to display INFO messages only once - static.INFO = [] + ModemParam.scatter = [] # self.request.sendall(sock_data) threading.Event().wait(0.15) @@ -127,7 +126,7 @@ def receive_from_client(self): # iterate thorugh data list for commands in data: - if self.server.server_address[1] == static.PORT: + if self.server.server_address[1] == TNC.port: self.process_tnc_commands(commands) else: self.process_daemon_commands(commands) @@ -354,14 +353,14 @@ def process_tnc_commands(self, data): def tnc_set_listen(self, received_json): try: - static.LISTEN = received_json["state"] in ['true', 'True', True, "ON", "on"] + TNC.listen = received_json["state"] in ['true', 'True', True, "ON", "on"] command_response("listen", True) - # if tnc is connected, force disconnect when static.LISTEN == False - if not static.LISTEN and static.ARQ_SESSION_STATE not in ["disconnecting", "disconnected", "failed"]: + # if tnc is connected, force disconnect when TNC.listen == False + if not TNC.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 - static.ARQ_SESSION_STATE = "disconnecting" + ARQ.arq_session_state = "disconnecting" command_response("disconnect", True) except Exception as err: @@ -372,15 +371,15 @@ def tnc_set_listen(self, received_json): def tnc_set_record_audio(self, received_json): try: - if not static.AUDIO_RECORD: - static.AUDIO_RECORD_FILE = wave.open(f"{int(time.time())}_audio_recording.wav", 'w') - static.AUDIO_RECORD_FILE.setnchannels(1) - static.AUDIO_RECORD_FILE.setsampwidth(2) - static.AUDIO_RECORD_FILE.setframerate(8000) - static.AUDIO_RECORD = True + if not AudioParam.audio_record: + AudioParam.audio_record_FILE = wave.open(f"{int(time.time())}_audio_recording.wav", 'w') + AudioParam.audio_record_FILE.setnchannels(1) + AudioParam.audio_record_FILE.setsampwidth(2) + AudioParam.audio_record_FILE.setframerate(8000) + AudioParam.audio_record = True else: - static.AUDIO_RECORD = False - static.AUDIO_RECORD_FILE.close() + AudioParam.audio_record = False + AudioParam.audio_record_FILE.close() command_response("respond_to_call", True) @@ -392,7 +391,7 @@ def tnc_set_record_audio(self, received_json): def tnc_set_respond_to_call(self, received_json): try: - static.RESPOND_TO_CALL = received_json["state"] in ['true', 'True', True] + TNC.respond_to_call = received_json["state"] in ['true', 'True', True] command_response("respond_to_call", True) except Exception as err: @@ -403,7 +402,7 @@ def tnc_set_respond_to_call(self, received_json): def tnc_set_respond_to_cq(self, received_json): try: - static.RESPOND_TO_CQ = received_json["state"] in ['true', 'True', True] + TNC.respond_to_cq = received_json["state"] in ['true', 'True', True] command_response("respond_to_cq", True) except Exception as err: @@ -414,7 +413,7 @@ def tnc_set_respond_to_cq(self, received_json): def tnc_set_tx_audio_level(self, received_json): try: - static.TX_AUDIO_LEVEL = int(received_json["value"]) + AudioParam.tx_audio_level = int(received_json["value"]) command_response("tx_audio_level", True) except Exception as err: @@ -481,7 +480,7 @@ def tnc_cqcqcq(self, received_json): def tnc_start_beacon(self, received_json): try: - static.BEACON_STATE = True + Beacon.beacon_state = True interval = int(received_json["parameter"]) DATA_QUEUE_TRANSMIT.put(["BEACON", interval, True]) command_response("start_beacon", True) @@ -496,7 +495,7 @@ def tnc_start_beacon(self, received_json): def tnc_stop_beacon(self, received_json): try: log.warning("[SCK] Stopping beacon!") - static.BEACON_STATE = False + Beacon.beacon_state = False DATA_QUEUE_TRANSMIT.put(["BEACON", None, False]) command_response("stop_beacon", True) except Exception as err: @@ -527,7 +526,7 @@ def tnc_ping_ping(self, received_json): mycallsign = helpers.bytes_to_callsign(mycallsign) except Exception: - mycallsign = static.MYCALLSIGN + mycallsign = Station.mycallsign DATA_QUEUE_TRANSMIT.put(["PING", mycallsign, dxcallsign]) command_response("ping", True) @@ -543,7 +542,7 @@ def tnc_ping_ping(self, received_json): def tnc_arq_connect(self, received_json): # pause our beacon first - static.BEACON_PAUSE = True + Beacon.beacon_pause = True # check for connection attempts key try: @@ -561,7 +560,7 @@ def tnc_arq_connect(self, received_json): mycallsign = helpers.bytes_to_callsign(mycallsign) except Exception: - mycallsign = static.MYCALLSIGN + mycallsign = Station.mycallsign # additional step for being sure our callsign is correctly # in case we are not getting a station ssid @@ -569,11 +568,11 @@ def tnc_arq_connect(self, received_json): dxcallsign = helpers.callsign_to_bytes(dxcallsign) dxcallsign = helpers.bytes_to_callsign(dxcallsign) - if static.ARQ_SESSION_STATE not in ["disconnected", "failed"]: + 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:{static.DXCALLSIGN}", + e=f"already connected to station:{Station.dxcallsign}", command=received_json, ) else: @@ -593,24 +592,24 @@ def tnc_arq_connect(self, received_json): command=received_json, ) # allow beacon transmission again - static.BEACON_PAUSE = False + Beacon.beacon_pause = False # allow beacon transmission again - static.BEACON_PAUSE = False + Beacon.beacon_pause = False def tnc_arq_disconnect(self, received_json): try: - if static.ARQ_SESSION_STATE not in ["disconnecting", "disconnected", "failed"]: + 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 - static.ARQ_SESSION_STATE = "disconnecting" + ARQ.arq_session_state = "disconnecting" command_response("disconnect", True) else: command_response("disconnect", False) log.warning( "[SCK] Disconnect command not possible", - state=static.ARQ_SESSION_STATE, + state=ARQ.arq_session_state, command=received_json, ) except Exception as err: @@ -622,13 +621,13 @@ def tnc_arq_disconnect(self, received_json): ) def tnc_arq_send_raw(self, received_json): - static.BEACON_PAUSE = True + Beacon.beacon_pause = True # wait some random time helpers.wait(randrange(5, 25, 5) / 10.0) # we need to warn if already in arq state - if static.ARQ_STATE: + if ARQ.arq_state: command_response("send_raw", False) log.warning( "[SCK] Send raw command execution warning", @@ -638,19 +637,19 @@ def tnc_arq_send_raw(self, received_json): ) try: - if not static.ARQ_SESSION: + 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) - static.DXCALLSIGN = dxcallsign - static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN) + Station.dxcallsign = dxcallsign + Station.dxcallsign_crc = helpers.get_crc_24(Station.dxcallsign) command_response("send_raw", True) else: - dxcallsign = static.DXCALLSIGN - static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN) + dxcallsign = Station.dxcallsign + Station.dxcallsign_crc = helpers.get_crc_24(Station.dxcallsign) mode = int(received_json["parameter"][0]["mode"]) n_frames = int(received_json["parameter"][0]["n_frames"]) @@ -663,7 +662,7 @@ def tnc_arq_send_raw(self, received_json): mycallsign = helpers.bytes_to_callsign(mycallsign) except Exception: - mycallsign = static.MYCALLSIGN + mycallsign = Station.mycallsign # check for connection attempts key try: @@ -697,11 +696,11 @@ def tnc_arq_send_raw(self, received_json): def tnc_arq_stop_transmission(self, received_json): try: - if static.TNC_STATE == "BUSY" or static.ARQ_STATE: + if TNC.tnc_state == "BUSY" or ARQ.arq_state: DATA_QUEUE_TRANSMIT.put(["STOP"]) log.warning("[SCK] Stopping transmission!") - static.TNC_STATE = "IDLE" - static.ARQ_STATE = False + TNC.tnc_state = "IDLE" + ARQ.arq_state = False command_response("stop_transmission", True) except Exception as err: command_response("stop_transmission", False) @@ -803,7 +802,7 @@ def process_daemon_commands(self, data): if ( received_json["type"] == "set" and received_json["command"] == "start_tnc" - and not static.TNCSTARTED + and not Daemon.tncstarted ): self.daemon_start_tnc(received_json) @@ -821,18 +820,18 @@ def daemon_set_mycallsign(self, received_json): self.request.sendall(b"INVALID CALLSIGN") log.warning( "[SCK] SET MYCALL FAILED", - call=static.MYCALLSIGN, - crc=static.MYCALLSIGN_CRC.hex(), + call=Station.mycallsign, + crc=Station.mycallsign_crc.hex(), ) else: - static.MYCALLSIGN = bytes(callsign, "utf-8") - static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN) + 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=static.MYCALLSIGN, - crc=static.MYCALLSIGN_CRC.hex(), + call=Station.mycallsign, + crc=Station.mycallsign_crc.hex(), ) except Exception as err: command_response("mycallsign", False) @@ -846,8 +845,8 @@ def daemon_set_mygrid(self, received_json): self.request.sendall(b"INVALID GRID") command_response("mygrid", False) else: - static.MYGRID = bytes(mygrid, "utf-8") - log.info("[SCK] SET MYGRID", grid=static.MYGRID) + 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) @@ -928,12 +927,12 @@ def daemon_start_tnc(self, received_json): def daemon_stop_tnc(self, received_json): try: - static.TNCPROCESS.kill() + Daemon.tncprocess.kill() # unregister process from atexit to avoid process zombies - atexit.unregister(static.TNCPROCESS.kill) + atexit.unregister(Daemon.tncprocess.kill) log.warning("[SCK] Stopping TNC") - static.TNCSTARTED = False + Daemon.tncstarted = False command_response("stop_tnc", True) except Exception as err: command_response("stop_tnc", False) @@ -972,15 +971,15 @@ def send_daemon_state(): "command": "daemon_state", "daemon_state": [], "python_version": str(python_version), - "input_devices": static.AUDIO_INPUT_DEVICES, - "output_devices": static.AUDIO_OUTPUT_DEVICES, - "serial_devices": static.SERIAL_DEVICES, + "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": "0.1", } - if static.TNCSTARTED: + if Daemon.tncstarted: output["daemon_state"].append({"status": "running"}) else: output["daemon_state"].append({"status": "stopped"}) @@ -996,55 +995,55 @@ def send_tnc_state(): send the tnc state to network """ encoding = "utf-8" - output = { "command": "tnc_state", - "ptt_state": str(static.PTT_STATE), - "tnc_state": str(static.TNC_STATE), - "arq_state": str(static.ARQ_STATE), - "arq_session": str(static.ARQ_SESSION), - "arq_session_state": str(static.ARQ_SESSION_STATE), - "audio_dbfs": str(static.AUDIO_DBFS), - "snr": str(static.SNR), - "frequency": str(static.HAMLIB_FREQUENCY), - "rf_level": str(static.HAMLIB_RF), - "strength": str(static.HAMLIB_STRENGTH), - "alc": str(static.HAMLIB_ALC), - "audio_level": str(static.TX_AUDIO_LEVEL), - "audio_auto_tune": str(static.AUDIO_AUTO_TUNE), - "speed_level": str(static.ARQ_SPEED_LEVEL), - "mode": str(static.HAMLIB_MODE), - "bandwidth": str(static.HAMLIB_BANDWIDTH), - "fft": str(static.FFT), - "channel_busy": str(static.CHANNEL_BUSY), - "is_codec2_traffic": str(static.IS_CODEC2_TRAFFIC), - "scatter": static.SCATTER, + "ptt_state": str(HamlibParam.ptt_state), + "tnc_state": str(TNC.tnc_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), + "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(static.RX_MSG_BUFFER)), - "arq_bytes_per_minute": str(static.ARQ_BYTES_PER_MINUTE), - "arq_bytes_per_minute_burst": str(static.ARQ_BYTES_PER_MINUTE_BURST), - "arq_seconds_until_finish": str(static.ARQ_SECONDS_UNTIL_FINISH), - "arq_compression_factor": str(static.ARQ_COMPRESSION_FACTOR), - "arq_transmission_percent": str(static.ARQ_TRANSMISSION_PERCENT), - "speed_list": static.SPEED_LIST, - "total_bytes": str(static.TOTAL_BYTES), - "beacon_state": str(static.BEACON_STATE), + "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_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": [], - "mycallsign": str(static.MYCALLSIGN, encoding), - "mygrid": str(static.MYGRID, encoding), - "dxcallsign": str(static.DXCALLSIGN, encoding), - "dxgrid": str(static.DXGRID, encoding), - "hamlib_status": static.HAMLIB_STATUS, - "listen": str(static.LISTEN), - "audio_recording": str(static.AUDIO_RECORD), + "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(TNC.listen), + "audio_recording": str(AudioParam.audio_record), } # add heard stations to heard stations object - for heard in static.HEARD_STATIONS: + for heard in TNC.heard_stations: output["stations"].append( { - "dxcallsign": str(heard[0], "utf-8"), - "dxgrid": str(heard[1], "utf-8"), + "dxcallsign": str(heard[0], encoding), + "dxgrid": str(heard[1], encoding), "timestamp": heard[2], "datatype": heard[3], "snr": heard[4], diff --git a/tnc/static.py b/tnc/static.py index 953e5d2bf..868557661 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -5,13 +5,188 @@ @author: DJ2LS Here we are saving application wide variables and stats, which have to be accessed everywhere. -Not nice, suggestions are appreciated :-) """ +from dataclasses import dataclass, field +from typing import List import subprocess from enum import Enum -VERSION = "0.8.1-alpha" + +# CHANNEL_STATE = 'RECEIVING_SIGNALLING' +# disconnected, connecting, connected, disconnecting, failed +# ------- RX BUFFER + +@dataclass +class ARQ: + bytes_per_minute: int = 0 + arq_transmission_percent: int = 0 + arq_compression_factor: int = 0 + arq_speed_level: int = 0 + arq_bits_per_second_burst: int = 0 + arq_bits_per_second: int = 0 + arq_seconds_until_finish: int = 0 + rx_buffer_size: int = 16 + rx_frame_buffer: bytes = b"" + rx_burst_buffer =[] + arq_session_state: str = "disconnected" + arq_session: bool = False + arq_state: bool = False + # ARQ PROTOCOL VERSION + # v.5 - signalling frame uses datac0 + # v.6 - signalling frame uses datac13 + arq_protocol_version: int = 6 + total_bytes: int = 0 + speed_list = [] + # set save to folder state for allowing downloading files to local file system + arq_save_to_folder: bool = False + bytes_per_minute_burst: int = 0 + rx_msg_buffer = [] + + +@dataclass +class AudioParam: + tx_audio_level: int = 50 + audio_input_devices = [] + audio_output_devices = [] + audio_input_device: int = -2 + audio_output_device: int = -2 + audio_record: bool = False + audio_record_file = '' + buffer_overflow_counter = [] + audio_auto_tune: bool = False + # Audio TCI Support + audio_enable_tci: bool = False + audio_dbfs: int = 0 + fft = [] + enable_fft: bool = True + + +@dataclass +class Beacon: + beacon_state: bool = False + beacon_pause: bool = False + +@dataclass +class Channel: + pass + +@dataclass +class Daemon: + tncprocess: subprocess.Popen + tncstarted: bool = False + port: int = 3001 + serial_devices = [] + +@dataclass +class HamlibParam: + alc: int = 0 + hamlib_frequency: int = 0 + hamlib_strength: int = 0 + hamlib_radiocontrol: str = "disabled" + hamlib_rigctld_ip: str = "127.0.0.1" + hamlib_rigctld_port: str = "4532" + ptt_state: bool = False + hamlib_bandwidth: int = 0 + hamlib_status: str = "unknown/disconnected" + hamlib_mode: str = "" + hamlib_rf: int = 0 + +@dataclass +class ModemParam: + tuning_range_fmin: float = -50.0 + tuning_range_fmax: float = 50.0 + channel_busy: bool = False + channel_busy_slot = [False] * 5 + snr: float = 0 + is_codec2_traffic: bool = False # true if we have codec2 signalling mode traffic on channel + frequency_offset: float = 0 + tx_delay: int = 0 # delay in ms before sending modulation for triggering VOX for example or slow PTT radios + enable_scatter: bool = False + scatter = [] + +@dataclass +class Station: + mycallsign: bytes = b"AA0AA" + mycallsign_crc: bytes = b"A" + dxcallsign: bytes = b"ZZ9YY" + dxcallsign_crc: bytes = b"A" + mygrid: bytes = b"" + dxgrid: bytes = b"" + ssid_list = [] # ssid list we are responding to + + +@dataclass +class Statistics: + pass + +@dataclass +class TCIParam: + ip: str = '127.0.0.1' + port: int = '9000' + +@dataclass +class TNC: + version = "0.9.0-alpha-exp.6" + host: str = "0.0.0.0" + port: int = 3000 + SOCKET_TIMEOUT: int = 1 # seconds + tnc_state: str = "IDLE" + enable_explorer = False + enable_stats = False + transmitting: bool = False + low_bandwidth_mode: bool = False + enable_fsk: bool = False + respond_to_cq: bool = True + respond_to_call: bool = True # respond to cq, ping, connection request, file request if not in session + heard_stations = [] + listen: bool = True + + # ------------ + + +class FRAME_TYPE(Enum): + """Lookup for frame types""" + + BURST_01 = 10 + BURST_02 = 11 + BURST_03 = 12 + BURST_04 = 13 + # ... + BURST_51 = 50 + BURST_ACK = 60 + FR_ACK = 61 + FR_REPEAT = 62 + FR_NACK = 63 + BURST_NACK = 64 + CQ = 200 + QRV = 201 + PING = 210 + PING_ACK = 211 + IS_WRITING = 215 + ARQ_SESSION_OPEN = 221 + ARQ_SESSION_HB = 222 + ARQ_SESSION_CLOSE = 223 + ARQ_DC_OPEN_W = 225 + ARQ_DC_OPEN_ACK_W = 226 + ARQ_DC_OPEN_N = 227 + ARQ_DC_OPEN_ACK_N = 228 + ARQ_STOP = 249 + BEACON = 250 + FEC = 251 + IDENT = 254 + TEST_FRAME = 255 + +# --------------------------------------------------------------- +# DON'T USE THESE SETTINGS ANYMORE +# --------------------------------------------------------------- +# Fixme: REMOVE THESE OLD SETTINGS! +# REASON: For some reason ctests are failing when using dataclasses. +# We need to figure out whats happening and why the tests are failing. + + +CHANNEL_BUSY_SLOT = [False] * 5 + ENABLE_EXPLORER = False ENABLE_STATS = False @@ -87,15 +262,15 @@ TCI_IP: str = '127.0.0.1' TCI_PORT: int = '9000' - - AUDIO_DBFS: int = 0 FFT: list = [0] ENABLE_FFT: bool = True CHANNEL_BUSY: bool = False # ARQ PROTOCOL VERSION -ARQ_PROTOCOL_VERSION: int = 5 +# v.5 - signalling frame uses datac0 +# v.6 - signalling frame uses datac13 +ARQ_PROTOCOL_VERSION: int = 6 # ARQ statistics SPEED_LIST: list = [] @@ -138,33 +313,4 @@ # ------- CODEC2 SETTINGS TUNING_RANGE_FMIN: float = -50.0 TUNING_RANGE_FMAX: float = 50.0 -IS_CODEC2_TRAFFIC: bool = False # true if we have codec2 signalling mode traffic on channel - -class FRAME_TYPE(Enum): - """Lookup for frame types""" - - BURST_01 = 10 - # ... - BURST_51 = 50 - BURST_ACK = 60 - FR_ACK = 61 - FR_REPEAT = 62 - FR_NACK = 63 - BURST_NACK = 64 - CQ = 200 - QRV = 201 - PING = 210 - PING_ACK = 211 - IS_WRITING = 215 - ARQ_SESSION_OPEN = 221 - ARQ_SESSION_HB = 222 - ARQ_SESSION_CLOSE = 223 - ARQ_DC_OPEN_W = 225 - ARQ_DC_OPEN_ACK_W = 226 - ARQ_DC_OPEN_N = 227 - ARQ_DC_OPEN_ACK_N = 228 - ARQ_STOP = 249 - BEACON = 250 - FEC = 251 - IDENT = 254 - TEST_FRAME = 255 +IS_CODEC2_TRAFFIC: bool = False # true if we have codec2 signalling mode traffic on channel diff --git a/tnc/stats.py b/tnc/stats.py index 96338da8e..4fb9731cb 100644 --- a/tnc/stats.py +++ b/tnc/stats.py @@ -13,6 +13,7 @@ import ujson as json import structlog import static +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, TCIParam, TNC log = structlog.get_logger("stats") @@ -25,29 +26,29 @@ def push(self, frame_nack_counter, status, duration): crcerror = status in ["crc_error", "wrong_crc"] # get avg snr try: - snr_raw = [item["snr"] for item in static.SPEED_LIST] + snr_raw = [item["snr"] for item in ARQ.speed_list] avg_snr = round(sum(snr_raw) / len(snr_raw), 2 ) except Exception: avg_snr = 0 headers = {"Content-Type": "application/json"} station_data = { - 'callsign': str(static.MYCALLSIGN, "utf-8"), - 'dxcallsign': str(static.DXCALLSIGN, "utf-8"), - 'gridsquare': str(static.MYGRID, "utf-8"), - 'dxgridsquare': str(static.DXGRID, "utf-8"), - 'frequency': 0 if static.HAMLIB_FREQUENCY is None else static.HAMLIB_FREQUENCY, + 'callsign': str(Station.mycallsign, "utf-8"), + 'dxcallsign': str(Station.dxcallsign, "utf-8"), + 'gridsquare': str(Station.mygrid, "utf-8"), + 'dxgridsquare': str(Station.dxgrid, "utf-8"), + 'frequency': 0 if HamlibParam.hamlib_frequency is None else HamlibParam.hamlib_frequency, 'avgstrength': 0, 'avgsnr': avg_snr, - 'bytesperminute': static.ARQ_BYTES_PER_MINUTE, - 'filesize': static.TOTAL_BYTES, - 'compressionfactor': static.ARQ_COMPRESSION_FACTOR, + 'bytesperminute': ARQ.bytes_per_minute, + 'filesize': ARQ.total_bytes, + 'compressionfactor': ARQ.arq_compression_factor, 'nacks': frame_nack_counter, 'crcerror': crcerror, 'duration': duration, - 'percentage': static.ARQ_TRANSMISSION_PERCENT, + 'percentage': ARQ.arq_transmission_percent, 'status': status, - 'version': static.VERSION + 'version': TNC.version } station_data = json.dumps(station_data) diff --git a/tnc/tci.py b/tnc/tci.py index 6739d9a15..7b7b6ff28 100644 --- a/tnc/tci.py +++ b/tnc/tci.py @@ -7,8 +7,9 @@ import numpy as np import time from queues import AUDIO_TRANSMIT_QUEUE, AUDIO_RECEIVED_QUEUE +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, TCIParam, TNC -class TCI: +class TCICtrl: def __init__(self, hostname='127.0.0.1', port=50001): # websocket.enableTrace(True) self.log = structlog.get_logger("TCI")