Merge pull request #259 from DJ2LS/ls-arq

WIP: Move to session id instead of callsign crc check
This commit is contained in:
DJ2LS 2022-11-03 09:27:49 +01:00 committed by GitHub
commit aaea0d1509
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 560 additions and 346 deletions

View file

@ -13,12 +13,18 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set up Python 3.9
uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Install packages - name: Install packages
shell: bash shell: bash
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install octave octave-common octave-signal sox python3 python3-pip portaudio19-dev python3-pyaudio sudo apt-get install octave octave-common octave-signal sox portaudio19-dev python3-pyaudio
pip3 install psutil crcengine ujson pyserial numpy structlog sounddevice pip3 install psutil crcengine ujson pyserial numpy structlog sounddevice pyaudio
pip3 install pytest pytest-rerunfailures pip3 install pytest pytest-rerunfailures
- name: Build codec2 - name: Build codec2

View file

@ -1458,11 +1458,10 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
document.getElementById("beaconInterval").disabled = false; document.getElementById("beaconInterval").disabled = false;
} }
// RMS // RMS
/* var rms_level = (arg.rms_level / 32767) * 100
var rms_level = Math.round((arg.rms_level/60) * 100)
document.getElementById("rms_level").setAttribute("aria-valuenow", rms_level); document.getElementById("rms_level").setAttribute("aria-valuenow", rms_level);
document.getElementById("rms_level").setAttribute("style", "width:" + rms_level + "%;"); document.getElementById("rms_level").setAttribute("style", "width:" + rms_level + "%;");
*/
// SET FREQUENCY // SET FREQUENCY
document.getElementById("frequency").innerHTML = arg.frequency; document.getElementById("frequency").innerHTML = arg.frequency;

View file

@ -746,11 +746,11 @@
<div class="card-body p-2"> <div class="card-body p-2">
<div class="progress mb-0" style="height: 15px;"> <div class="progress mb-0" style="height: 15px;">
<div class="progress-bar progress-bar-striped bg-primary" id="rms_level" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div> <div class="progress-bar progress-bar-striped bg-primary" id="rms_level" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
<p class="justify-content-center d-flex position-absolute w-100">RX AUDIO LEVEL - not implemented yet</p> <p class="justify-content-center d-flex position-absolute w-100">RX AUDIO LEVEL</p>
</div> </div>
<div class="progress mb-0" style="height: 5px;"> <div class="progress mb-0" style="height: 5px;">
<div class="progress-bar progress-bar-striped bg-warning" role="progressbar" style="width: 10%" aria-valuenow="10" aria-valuemin="0" aria-valuemax="100"></div> <div class="progress-bar progress-bar-striped bg-warning" role="progressbar" style="width: 1%" aria-valuenow="1" aria-valuemin="0" aria-valuemax="100"></div>
<div class="progress-bar bg-success" role="progressbar" style="width: 80%" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100"></div> <div class="progress-bar bg-success" role="progressbar" style="width: 89%" aria-valuenow="89" aria-valuemin="0" aria-valuemax="100"></div>
<div class="progress-bar progress-bar-striped bg-danger" role="progressbar" style="width: 10%" aria-valuenow="10" aria-valuemin="0" aria-valuemax="100"></div> <div class="progress-bar progress-bar-striped bg-danger" role="progressbar" style="width: 10%" aria-valuenow="10" aria-valuemin="0" aria-valuemax="100"></div>
</div> </div>
</div> </div>

View file

@ -15,6 +15,7 @@ Uses util_datac0.py in separate process to perform the data transfer.
""" """
import multiprocessing import multiprocessing
from random import randbytes
import sys import sys
import time import time
@ -62,16 +63,23 @@ def t_create_frame(frame_type: int, mycall: str, dxcall: str) -> bytearray:
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes) dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
dxcallsign_crc = helpers.get_crc_24(dxcallsign) dxcallsign_crc = helpers.get_crc_24(dxcallsign)
# frame = bytearray(14)
# frame[:1] = bytes([frame_type])
# frame[1:4] = dxcallsign_crc
# frame[4:7] = mycallsign_crc
# frame[7:13] = mycallsign_bytes
session_id = randbytes(1)
frame = bytearray(14) frame = bytearray(14)
frame[:1] = bytes([frame_type]) frame[:1] = bytes([frame_type])
frame[1:4] = dxcallsign_crc frame[1:2] = session_id
frame[4:7] = mycallsign_crc frame[2:5] = dxcallsign_crc
frame[7:13] = mycallsign_bytes frame[5:8] = mycallsign_crc
frame[8:14] = mycallsign_bytes
return frame return frame
def t_create_session_close(mycall: str, dxcall: str) -> bytearray: def t_create_session_close_old(mycall: str, dxcall: str) -> bytearray:
""" """
Generate the session_close frame. Generate the session_close frame.
@ -85,6 +93,23 @@ def t_create_session_close(mycall: str, dxcall: str) -> bytearray:
return t_create_frame(223, mycall, dxcall) return t_create_frame(223, mycall, dxcall)
def t_create_session_close(session_id: bytes) -> bytearray:
"""
Generate the session_close frame.
:param session_id: Session to close
:type mycall: int
:return: Bytearray of the requested frame
:rtype: bytearray
"""
# return t_create_frame(223, mycall, dxcall)
frame = bytearray(14)
frame[:1] = bytes([223])
frame[1:2] = session_id
return frame
def t_create_start_session(mycall: str, dxcall: str) -> bytearray: def t_create_start_session(mycall: str, dxcall: str) -> bytearray:
""" """
Generate the create_session frame. Generate the create_session frame.
@ -150,18 +175,24 @@ def t_foreign_disconnect(mycall: str, dxcall: str):
assert static.ARQ_SESSION_STATE == "connecting" assert static.ARQ_SESSION_STATE == "connecting"
# Set up a frame from a non-associated station. # Set up a frame from a non-associated station.
foreigncall_bytes = helpers.callsign_to_bytes("ZZ0ZZ-0") # foreigncall_bytes = helpers.callsign_to_bytes("ZZ0ZZ-0")
foreigncall = helpers.bytes_to_callsign(foreigncall_bytes) # foreigncall = helpers.bytes_to_callsign(foreigncall_bytes)
close_frame = t_create_session_close("ZZ0ZZ-0", "ZZ0ZZ-0") # close_frame = t_create_session_close_old("ZZ0ZZ-0", "ZZ0ZZ-0")
open_session = create_frame[1:2]
wrong_session = randbytes(1)
while wrong_session == open_session:
wrong_session = randbytes(1)
close_frame = t_create_session_close(wrong_session)
print_frame(close_frame) 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."
assert ( # assert (
helpers.check_callsign(foreigncall, bytes(close_frame[4:7]))[0] is True # helpers.check_callsign(static.DXCALLSIGN, bytes(close_frame[4:7]))[0] is False
), f"{helpers.get_crc_24(foreigncall)} != {bytes(close_frame[4:7])} but should be equal." # ), f"{helpers.get_crc_24(static.DXCALLSIGN)} == {bytes(close_frame[4:7])} but should be not equal."
# assert (
# helpers.check_callsign(foreigncall, bytes(close_frame[4:7]))[0] is True
# ), f"{helpers.get_crc_24(foreigncall)} != {bytes(close_frame[4:7])} but should be equal."
# Send the non-associated session close frame to the TNC # Send the non-associated session close frame to the TNC
tnc.received_session_close(close_frame) tnc.received_session_close(close_frame)
@ -221,7 +252,9 @@ def t_valid_disconnect(mycall: str, dxcall: str):
assert static.ARQ_SESSION_STATE == "connecting" assert static.ARQ_SESSION_STATE == "connecting"
# Create packet to be 'received' by this station. # Create packet to be 'received' by this station.
close_frame = t_create_session_close(mycall=dxcall, dxcall=mycall) # close_frame = t_create_session_close_old(mycall=dxcall, dxcall=mycall)
open_session = create_frame[1:2]
close_frame = t_create_session_close(open_session)
print_frame(close_frame) print_frame(close_frame)
tnc.received_session_close(close_frame) tnc.received_session_close(close_frame)
@ -241,7 +274,7 @@ def t_valid_disconnect(mycall: str, dxcall: str):
@pytest.mark.parametrize("mycall", ["AA1AA-2", "DE2DE-0", "E4AWQ-4"]) @pytest.mark.parametrize("mycall", ["AA1AA-2", "DE2DE-0", "E4AWQ-4"])
@pytest.mark.parametrize("dxcall", ["AA9AA-1", "DE2ED-0", "F6QWE-3"]) @pytest.mark.parametrize("dxcall", ["AA9AA-1", "DE2ED-0", "F6QWE-3"])
@pytest.mark.flaky(reruns=2) # @pytest.mark.flaky(reruns=2)
def test_foreign_disconnect(mycall: str, dxcall: str): def test_foreign_disconnect(mycall: str, dxcall: str):
proc = multiprocessing.Process(target=t_foreign_disconnect, args=(mycall, dxcall)) proc = multiprocessing.Process(target=t_foreign_disconnect, args=(mycall, dxcall))
# print("Starting threads.") # print("Starting threads.")

View file

@ -42,8 +42,8 @@ def get_audio_devices():
proc.start() proc.start()
proc.join() proc.join()
#log.debug("[AUD] get_audio_devices: input_devices:", list=f"{proxy_input_devices}") log.debug("[AUD] get_audio_devices: input_devices:", list=f"{proxy_input_devices}")
#log.debug("[AUD] get_audio_devices: output_devices:", list=f"{proxy_output_devices}") log.debug("[AUD] get_audio_devices: output_devices:", list=f"{proxy_output_devices}")
return list(proxy_input_devices), list(proxy_output_devices) return list(proxy_input_devices), list(proxy_output_devices)

View file

@ -23,7 +23,8 @@ class FREEDV_MODE(Enum):
""" """
Enumeration for codec2 modes and names Enumeration for codec2 modes and names
""" """
sig0 = 14
sig1 = 14
datac0 = 14 datac0 = 14
datac1 = 10 datac1 = 10
datac3 = 12 datac3 = 12
@ -103,6 +104,9 @@ api.freedv_open_advanced.restype = ctypes.c_void_p
api.freedv_get_bits_per_modem_frame.argtype = [ctypes.c_void_p] # type: ignore api.freedv_get_bits_per_modem_frame.argtype = [ctypes.c_void_p] # type: ignore
api.freedv_get_bits_per_modem_frame.restype = ctypes.c_int api.freedv_get_bits_per_modem_frame.restype = ctypes.c_int
api.freedv_get_modem_extended_stats.argtype = [ctypes.c_void_p, ctypes.c_void_p]
api.freedv_get_modem_extended_stats.restype = ctypes.c_int
api.freedv_nin.argtype = [ctypes.c_void_p] # type: ignore api.freedv_nin.argtype = [ctypes.c_void_p] # type: ignore
api.freedv_nin.restype = ctypes.c_int api.freedv_nin.restype = ctypes.c_int
@ -208,8 +212,8 @@ api.FREEDV_MODE_FSK_LDPC_1_ADV.tone_spacing = 200
api.FREEDV_MODE_FSK_LDPC_1_ADV.codename = "H_256_512_4".encode("utf-8") # code word api.FREEDV_MODE_FSK_LDPC_1_ADV.codename = "H_256_512_4".encode("utf-8") # code word
# ------- MODEM STATS STRUCTURES # ------- MODEM STATS STRUCTURES
MODEM_STATS_NC_MAX = 50 + 1 MODEM_STATS_NC_MAX = 50 + 1 * 2
MODEM_STATS_NR_MAX = 160 MODEM_STATS_NR_MAX = 160 * 2
MODEM_STATS_ET_MAX = 8 MODEM_STATS_ET_MAX = 8
MODEM_STATS_EYE_IND_MAX = 160 MODEM_STATS_EYE_IND_MAX = 160
MODEM_STATS_NSPEC = 512 MODEM_STATS_NSPEC = 512
@ -233,10 +237,12 @@ class MODEMSTATS(ctypes.Structure):
("pre", ctypes.c_int), ("pre", ctypes.c_int),
("post", ctypes.c_int), ("post", ctypes.c_int),
("uw_fails", ctypes.c_int), ("uw_fails", ctypes.c_int),
("rx_eye", (ctypes.c_float * MODEM_STATS_ET_MAX) * MODEM_STATS_EYE_IND_MAX),
("neyetr", ctypes.c_int), # How many eye traces are plotted ("neyetr", ctypes.c_int), # How many eye traces are plotted
("neyesamp", ctypes.c_int), # How many samples in the eye diagram ("neyesamp", ctypes.c_int), # How many samples in the eye diagram
("f_est", (ctypes.c_float * MODEM_STATS_MAX_F_EST)), ("f_est", (ctypes.c_float * MODEM_STATS_MAX_F_EST)),
("fft_buf", (ctypes.c_float * MODEM_STATS_NSPEC * 2)), ("fft_buf", (ctypes.c_float * MODEM_STATS_NSPEC * 2)),
("fft_cfg", ctypes.c_void_p)
] ]

View file

@ -13,7 +13,7 @@ import threading
import time import time
import uuid import uuid
import zlib import zlib
from random import randrange from random import randrange, randbytes
import codec2 import codec2
import helpers import helpers
@ -44,7 +44,11 @@ class DATA:
self.data_queue_received = DATA_QUEUE_RECEIVED self.data_queue_received = DATA_QUEUE_RECEIVED
# length of signalling frame # length of signalling frame
self.length_sig_frame = 14 self.length_sig0_frame = 14
self.length_sig1_frame = 14
# hold session id
self.session_id = bytes(1)
# ------- ARQ SESSION # ------- ARQ SESSION
self.arq_file_transfer = False self.arq_file_transfer = False
@ -53,6 +57,9 @@ class DATA:
self.arq_session_timeout = 30 self.arq_session_timeout = 30
self.session_connect_max_retries = 15 self.session_connect_max_retries = 15
# actual n retries of burst
self.tx_n_retry_of_burst = 0
self.transmission_uuid = "" self.transmission_uuid = ""
self.data_channel_last_received = 0.0 # time of last "live sign" of a frame self.data_channel_last_received = 0.0 # time of last "live sign" of a frame
@ -72,6 +79,7 @@ class DATA:
# 3 bytes for the EOF End of File indicator in a data frame # 3 bytes for the EOF End of File indicator in a data frame
self.data_frame_eof = b"EOF" self.data_frame_eof = b"EOF"
self.tx_n_max_retries_per_burst = 50
self.rx_n_max_retries_per_burst = 50 self.rx_n_max_retries_per_burst = 50
self.n_retries_per_burst = 0 self.n_retries_per_burst = 0
@ -128,7 +136,11 @@ class DATA:
self.rx_frame_bof_received = False self.rx_frame_bof_received = False
self.rx_frame_eof_received = False self.rx_frame_eof_received = False
self.transmission_timeout = 360 # transmission timeout in seconds # 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.transmission_timeout = 500 # transmission timeout in seconds
# Dictionary of functions and log messages used in process_data # Dictionary of functions and log messages used in process_data
# instead of a long series of if-elif-else statements. # instead of a long series of if-elif-else statements.
@ -278,23 +290,33 @@ class DATA:
# bytes_out[2:5] == transmission # bytes_out[2:5] == transmission
# we could also create an own function, which returns True. # we could also create an own function, which returns True.
frametype = int.from_bytes(bytes(bytes_out[:1]), "big") frametype = int.from_bytes(bytes(bytes_out[:1]), "big")
# check for callsign CRC
_valid1, _ = helpers.check_callsign(self.mycallsign, bytes(bytes_out[1:4])) _valid1, _ = helpers.check_callsign(self.mycallsign, bytes(bytes_out[1:4]))
_valid2, _ = helpers.check_callsign(self.mycallsign, bytes(bytes_out[2:5])) _valid2, _ = helpers.check_callsign(self.mycallsign, bytes(bytes_out[2:5]))
# check for session ID
# signalling frames
_valid3 = helpers.check_session_id(self.session_id, bytes(bytes_out[1:2]))
# arq data frames
_valid4 = helpers.check_session_id(self.session_id, bytes(bytes_out[2:3]))
if ( if (
_valid1 _valid1
or _valid2 or _valid2
or _valid3
or _valid4
or frametype or frametype
in [ in [
FR_TYPE.CQ.value, FR_TYPE.CQ.value,
FR_TYPE.QRV.value, FR_TYPE.QRV.value,
FR_TYPE.PING.value, FR_TYPE.PING.value,
FR_TYPE.BEACON.value, FR_TYPE.BEACON.value,
] ]
): ):
# CHECK IF FRAMETYPE IS BETWEEN 10 and 50 ------------------------ # CHECK IF FRAMETYPE IS BETWEEN 10 and 50 ------------------------
frame = frametype - 10 # frame = frametype - 10
n_frames_per_burst = int.from_bytes(bytes(bytes_out[1:2]), "big") # n_frames_per_burst = int.from_bytes(bytes(bytes_out[1:2]), "big")
# Dispatch activity based on received frametype # Dispatch activity based on received frametype
if frametype in self.rx_dispatcher: if frametype in self.rx_dispatcher:
@ -339,7 +361,7 @@ class DATA:
def enqueue_frame_for_tx( def enqueue_frame_for_tx(
self, self,
frame_to_tx: bytearray, frame_to_tx: list[bytearray],
c2_mode=FREEDV_MODE.datac0.value, c2_mode=FREEDV_MODE.datac0.value,
copies=1, copies=1,
repeat_delay=0, repeat_delay=0,
@ -348,7 +370,7 @@ class DATA:
Send (transmit) supplied frame to TNC Send (transmit) supplied frame to TNC
:param frame_to_tx: Frame data to send :param frame_to_tx: Frame data to send
:type frame_to_tx: bytearray :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 14 (datac0)
:type c2_mode: int, optional :type c2_mode: int, optional
:param copies: Number of frame copies to send, defaults to 1 :param copies: Number of frame copies to send, defaults to 1
@ -361,7 +383,7 @@ class DATA:
# Set the TRANSMITTING flag before adding an object to the transmit queue # Set the TRANSMITTING flag before adding an object to the transmit queue
# TODO: This is not that nice, we could improve this somehow # TODO: This is not that nice, we could improve this somehow
static.TRANSMITTING = True static.TRANSMITTING = True
modem.MODEM_TRANSMIT_QUEUE.put([c2_mode, copies, repeat_delay, [frame_to_tx]]) modem.MODEM_TRANSMIT_QUEUE.put([c2_mode, copies, repeat_delay, frame_to_tx])
# Wait while transmitting # Wait while transmitting
while static.TRANSMITTING: while static.TRANSMITTING:
@ -390,32 +412,48 @@ class DATA:
json_data_out = json.dumps(jsondata) json_data_out = json.dumps(jsondata)
sock.SOCKET_QUEUE.put(json_data_out) sock.SOCKET_QUEUE.put(json_data_out)
def send_burst_ack_frame(self, snr) -> None: def send_ident_frame(self, transmit) -> None:
"""Build and send ACK frame for burst DATA frame""" """Build and send IDENT frame """
ack_frame = bytearray(self.length_sig_frame) ident_frame = bytearray(self.length_sig1_frame)
ack_frame[:1] = bytes([FR_TYPE.BURST_ACK.value]) ident_frame[:1] = bytes([FR_TYPE.IDENT.value])
ack_frame[1:4] = static.DXCALLSIGN_CRC ident_frame[1:self.length_sig1_frame] = self.mycallsign
ack_frame[4:7] = static.MYCALLSIGN_CRC
ack_frame[7:8] = bytes([int(snr)])
ack_frame[8:9] = bytes([int(self.speed_level)])
# Transmit frame # Transmit frame
self.enqueue_frame_for_tx(ack_frame) if transmit:
self.enqueue_frame_for_tx([ident_frame], c2_mode=FREEDV_MODE.datac0.value)
else:
return ident_frame
def send_burst_ack_frame(self, snr) -> None:
"""Build and send ACK frame for burst DATA frame"""
ack_frame = bytearray(self.length_sig1_frame)
ack_frame[:1] = bytes([FR_TYPE.BURST_ACK.value])
# ack_frame[1:4] = static.DXCALLSIGN_CRC
# ack_frame[4:7] = static.MYCALLSIGN_CRC
ack_frame[1:2] = self.session_id
ack_frame[2:3] = bytes([int(snr)])
ack_frame[3:4] = bytes([int(self.speed_level)])
# Transmit frame
self.enqueue_frame_for_tx([ack_frame], c2_mode=FREEDV_MODE.sig1.value)
def send_data_ack_frame(self, snr) -> None: def send_data_ack_frame(self, snr) -> None:
"""Build and send ACK frame for received DATA frame""" """Build and send ACK frame for received DATA frame"""
ack_frame = bytearray(self.length_sig_frame) ack_frame = bytearray(self.length_sig1_frame)
ack_frame[:1] = bytes([FR_TYPE.FR_ACK.value]) ack_frame[:1] = bytes([FR_TYPE.FR_ACK.value])
ack_frame[1:4] = static.DXCALLSIGN_CRC ack_frame[1:2] = self.session_id
ack_frame[4:7] = static.MYCALLSIGN_CRC # ack_frame[1:4] = static.DXCALLSIGN_CRC
ack_frame[7:8] = bytes([int(snr)]) # ack_frame[4:7] = static.MYCALLSIGN_CRC
ack_frame[8:9] = bytes([int(self.speed_level)]) # ack_frame[7:8] = bytes([int(snr)])
# ack_frame[8:9] = bytes([int(self.speed_level)])
# Transmit frame # Transmit frame
self.enqueue_frame_for_tx(ack_frame, copies=3, repeat_delay=100) # 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=3, repeat_delay=0)
def send_retransmit_request_frame(self, freedv) -> None: def send_retransmit_request_frame(self, freedv) -> None:
# check where a None is in our burst buffer and do frame+1, beacuse lists start at 0 # 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. # FIXME: Check to see if there's a `frame - 1` in the receive portion. Remove both if there is.
missing_frames = [ missing_frames = [
frame + 1 frame + 1
@ -429,51 +467,60 @@ class DATA:
codec2.api.freedv_set_frames_per_burst(freedv, len(missing_frames)) 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: 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 # then create a repeat frame
rpt_frame = bytearray(self.length_sig_frame) rpt_frame = bytearray(self.length_sig1_frame)
rpt_frame[:1] = bytes([FR_TYPE.FR_REPEAT.value]) rpt_frame[:1] = bytes([FR_TYPE.FR_REPEAT.value])
rpt_frame[1:4] = static.DXCALLSIGN_CRC rpt_frame[1:2] = self.session_id
rpt_frame[4:7] = static.MYCALLSIGN_CRC # rpt_frame[1:4] = static.DXCALLSIGN_CRC
rpt_frame[7:13] = missing_frames # rpt_frame[4:7] = static.MYCALLSIGN_CRC
# rpt_frame[7:13] = missing_frames
self.log.info("[TNC] ARQ | RX | Requesting", frames=missing_frames) self.log.info("[TNC] ARQ | RX | Requesting", frames=missing_frames)
# Transmit frame # Transmit frame
self.enqueue_frame_for_tx(rpt_frame) self.enqueue_frame_for_tx([rpt_frame], c2_mode=FREEDV_MODE.sig1.value, copies=1, repeat_delay=0)
def send_burst_nack_frame(self, snr: float = 0) -> None: def send_burst_nack_frame(self, snr: float = 0) -> None:
"""Build and send NACK frame for received DATA frame""" """Build and send NACK frame for received DATA frame"""
nack_frame = bytearray(self.length_sig_frame) nack_frame = bytearray(self.length_sig1_frame)
nack_frame[:1] = bytes([FR_TYPE.FR_NACK.value]) nack_frame[:1] = bytes([FR_TYPE.FR_NACK.value])
nack_frame[1:4] = static.DXCALLSIGN_CRC nack_frame[1:2] = self.session_id
nack_frame[4:7] = static.MYCALLSIGN_CRC # nack_frame[1:4] = static.DXCALLSIGN_CRC
nack_frame[7:8] = bytes([int(snr)]) # nack_frame[4:7] = static.MYCALLSIGN_CRC
nack_frame[8:9] = bytes([int(self.speed_level)]) nack_frame[2:3] = bytes([int(snr)])
nack_frame[3:4] = bytes([int(self.speed_level)])
# TRANSMIT NACK FRAME FOR BURST # TRANSMIT NACK FRAME FOR BURST
self.enqueue_frame_for_tx(nack_frame) # TODO: Do we have to send ident frame?
# self.enqueue_frame_for_tx([ack_frame, self.send_ident_frame(False)], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0)
self.enqueue_frame_for_tx([nack_frame], c2_mode=FREEDV_MODE.sig1.value, copies=1, repeat_delay=0)
def send_burst_nack_frame_watchdog(self, snr: float = 0) -> None: def send_burst_nack_frame_watchdog(self, snr: float = 0) -> None:
"""Build and send NACK frame for watchdog timeout""" """Build and send NACK frame for watchdog timeout"""
nack_frame = bytearray(self.length_sig_frame) nack_frame = bytearray(self.length_sig1_frame)
nack_frame[:1] = bytes([FR_TYPE.BURST_NACK.value]) nack_frame[:1] = bytes([FR_TYPE.BURST_NACK.value])
nack_frame[1:4] = static.DXCALLSIGN_CRC nack_frame[1:2] = self.session_id
nack_frame[4:7] = static.MYCALLSIGN_CRC # nack_frame[1:4] = static.DXCALLSIGN_CRC
nack_frame[7:8] = bytes([int(snr)]) # nack_frame[4:7] = static.MYCALLSIGN_CRC
nack_frame[8:9] = bytes([int(self.speed_level)]) nack_frame[2:3] = bytes([int(snr)])
nack_frame[3:4] = bytes([int(self.speed_level)])
# TRANSMIT NACK FRAME FOR BURST # TRANSMIT NACK FRAME FOR BURST
self.enqueue_frame_for_tx(nack_frame) self.enqueue_frame_for_tx([nack_frame], c2_mode=FREEDV_MODE.sig1.value, copies=1, repeat_delay=0)
def send_disconnect_frame(self) -> None: def send_disconnect_frame(self) -> None:
"""Build and send a disconnect frame""" """Build and send a disconnect frame"""
disconnection_frame = bytearray(self.length_sig_frame) disconnection_frame = bytearray(self.length_sig1_frame)
disconnection_frame[:1] = bytes([FR_TYPE.ARQ_SESSION_CLOSE.value]) disconnection_frame[:1] = bytes([FR_TYPE.ARQ_SESSION_CLOSE.value])
disconnection_frame[1:4] = static.DXCALLSIGN_CRC disconnection_frame[1:2] = self.session_id
disconnection_frame[4:7] = static.MYCALLSIGN_CRC # disconnection_frame[1:4] = static.DXCALLSIGN_CRC
disconnection_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign) # disconnection_frame[4:7] = static.MYCALLSIGN_CRC
# TODO: Needed? disconnection_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign)
self.enqueue_frame_for_tx(disconnection_frame, copies=5, repeat_delay=250) # 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
# TODO: We need to wait some time between last arq related signalling frame and ident frame
# TODO: Maybe about 500ms - 1500ms to avoid confusion and too much PTT toggles
self.enqueue_frame_for_tx([disconnection_frame], c2_mode=FREEDV_MODE.sig0.value, copies=5, repeat_delay=0)
def arq_data_received( def arq_data_received(
self, data_in: bytes, bytes_per_frame: int, snr: float, freedv self, data_in: bytes, bytes_per_frame: int, snr: float, freedv
@ -491,11 +538,14 @@ class DATA:
# is intended for this station. # is intended for this station.
data_in = bytes(data_in) data_in = bytes(data_in)
# TODO: this seems not to work anymore
# get received crc for different mycall ssids # get received crc for different mycall ssids
# check if callsign ssid override # check if callsign ssid override
_, mycallsign = helpers.check_callsign( # _, mycallsign = helpers.check_callsign(
self.mycallsign, data_in[2:5] # self.mycallsign, data_in[2:5]
) # )
# attempt fixing this
mycallsign = self.mycallsign
# only process data if we are in ARQ and BUSY state else return to quit # only process data if we are in ARQ and BUSY state else return to quit
if not static.ARQ_STATE and static.TNC_STATE != "BUSY": if not static.ARQ_STATE and static.TNC_STATE != "BUSY":
@ -511,19 +561,20 @@ class DATA:
# Extract some important data from the frame # Extract some important data from the frame
# Get sequence number of burst frame # Get sequence number of burst frame
RX_N_FRAME_OF_BURST = int.from_bytes(bytes(data_in[:1]), "big") - 10 rx_n_frame_of_burst = int.from_bytes(bytes(data_in[:1]), "big") - 10
# Get number of bursts from received frame # Get number of bursts from received frame
RX_N_FRAMES_PER_BURST = int.from_bytes(bytes(data_in[1:2]), "big") 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". # 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. # We need this later for counting the "Nones" to detect missing data.
# Check if burst buffer has expected length else create it # Check if burst buffer has expected length else create it
if len(static.RX_BURST_BUFFER) != RX_N_FRAMES_PER_BURST: if len(static.RX_BURST_BUFFER) != rx_n_frames_per_burst:
static.RX_BURST_BUFFER = [None] * RX_N_FRAMES_PER_BURST static.RX_BURST_BUFFER = [None] * rx_n_frames_per_burst
# Append data to rx burst buffer # Append data to rx burst buffer
# [frame_type][n_frames_per_burst][CRC24][CRC24] # [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[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) self.log.debug("[TNC] static.RX_BURST_BUFFER", buffer=static.RX_BURST_BUFFER)
@ -540,7 +591,7 @@ class DATA:
# This is the ideal case because we received all data # This is the ideal case because we received all data
if None not in static.RX_BURST_BUFFER: if None not in static.RX_BURST_BUFFER:
# then iterate through burst buffer and stick the burst together # then iterate through burst buffer and stick the burst together
# the temp burst buffer is needed for checking, if we already recevied data # the temp burst buffer is needed for checking, if we already received data
temp_burst_buffer = b"" temp_burst_buffer = b""
for value in static.RX_BURST_BUFFER: for value in static.RX_BURST_BUFFER:
# static.RX_FRAME_BUFFER += static.RX_BURST_BUFFER[i] # static.RX_FRAME_BUFFER += static.RX_BURST_BUFFER[i]
@ -603,9 +654,8 @@ class DATA:
) )
static.ARQ_SPEED_LEVEL = self.speed_level static.ARQ_SPEED_LEVEL = self.speed_level
# Update modes we are listening to # Update modes we are listening to
self.set_listening_modes(self.mode_list[self.speed_level]) self.set_listening_modes(False, True, self.mode_list[self.speed_level])
# Create and send ACK frame # Create and send ACK frame
self.log.info("[TNC] ARQ | RX | SENDING ACK") self.log.info("[TNC] ARQ | RX | SENDING ACK")
@ -619,7 +669,7 @@ class DATA:
self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER) self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER)
) )
elif RX_N_FRAME_OF_BURST == RX_N_FRAMES_PER_BURST - 1: elif rx_n_frame_of_burst == rx_n_frames_per_burst - 1:
# We have "Nones" in our rx buffer, # We have "Nones" in our rx buffer,
# Check if we received last frame of burst - this is an indicator for missed frames. # 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 # With this way of doing this, we always MUST receive the last
@ -627,8 +677,8 @@ class DATA:
# TODO: See if a timeout on the send side with re-transmit last burst would help. # TODO: See if a timeout on the send side with re-transmit last burst would help.
self.log.debug( self.log.debug(
"[TNC] all frames in burst received:", "[TNC] all frames in burst received:",
frame=RX_N_FRAME_OF_BURST, frame=rx_n_frame_of_burst,
frames=RX_N_FRAMES_PER_BURST, frames=rx_n_frames_per_burst,
) )
self.send_retransmit_request_frame(freedv) self.send_retransmit_request_frame(freedv)
self.calculate_transfer_rate_rx( self.calculate_transfer_rate_rx(
@ -639,8 +689,8 @@ class DATA:
else: else:
self.log.error( self.log.error(
"[TNC] data_handler: Should not reach this point...", "[TNC] data_handler: Should not reach this point...",
frame=RX_N_FRAME_OF_BURST, frame=rx_n_frame_of_burst,
frames=RX_N_FRAMES_PER_BURST, frames=rx_n_frames_per_burst,
) )
# We have a BOF and EOF flag in our data. If we received both we received our frame. # We have a BOF and EOF flag in our data. If we received both we received our frame.
@ -649,7 +699,7 @@ class DATA:
bof_position = static.RX_FRAME_BUFFER.find(self.data_frame_bof) bof_position = static.RX_FRAME_BUFFER.find(self.data_frame_bof)
eof_position = static.RX_FRAME_BUFFER.find(self.data_frame_eof) eof_position = static.RX_FRAME_BUFFER.find(self.data_frame_eof)
# get total bytes per transmission information as soon we recevied a frame with a BOF # get total bytes per transmission information as soon we received a frame with a BOF
if bof_position >= 0: if bof_position >= 0:
payload = static.RX_FRAME_BUFFER[ payload = static.RX_FRAME_BUFFER[
@ -755,6 +805,7 @@ class DATA:
snr=snr, snr=snr,
crc=data_frame_crc.hex(), crc=data_frame_crc.hex(),
) )
self.send_data_ack_frame(snr) self.send_data_ack_frame(snr)
# Update statistics AFTER the frame ACK is sent # Update statistics AFTER the frame ACK is sent
self.calculate_transfer_rate_rx( self.calculate_transfer_rate_rx(
@ -807,15 +858,13 @@ class DATA:
""" """
self.arq_file_transfer = True self.arq_file_transfer = True
# 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
modem.RECEIVE_SIG1 = True
self.tx_n_retry_of_burst = 0 # retries we already sent data self.tx_n_retry_of_burst = 0 # retries we already sent data
# Maximum number of retries to send before declaring a frame is lost # Maximum number of retries to send before declaring a frame is lost
TX_N_MAX_RETRIES_PER_BURST = 50
TX_N_FRAMES_PER_BURST = n_frames_per_burst # amount of n frames per burst
# TIMEOUTS
BURST_ACK_TIMEOUT_SECONDS = 3.0 # timeout for burst acknowledges
DATA_FRAME_ACK_TIMEOUT_SECONDS = 3.0 # timeout for data frame acknowledges
RPT_ACK_TIMEOUT_SECONDS = 3.0 # timeout for rpt frame acknowledges
# save len of data_out to TOTAL_BYTES for our statistics --> kBytes # save len of data_out to TOTAL_BYTES for our statistics --> kBytes
# static.TOTAL_BYTES = round(len(data_out) / 1024, 2) # static.TOTAL_BYTES = round(len(data_out) / 1024, 2)
@ -867,9 +916,9 @@ class DATA:
# Iterate through data_out buffer # Iterate through data_out buffer
while not self.data_frame_ack_received and static.ARQ_STATE: while not self.data_frame_ack_received and static.ARQ_STATE:
# we have TX_N_MAX_RETRIES_PER_BURST attempts for sending a burst # we have self.tx_n_max_retries_per_burst attempts for sending a burst
for self.tx_n_retry_of_burst in range(TX_N_MAX_RETRIES_PER_BURST): for self.tx_n_retry_of_burst in range(self.tx_n_max_retries_per_burst):
data_mode = mode # data_mode = mode
# self.log.debug("[TNC] FIXED MODE:", mode=FREEDV_MODE(data_mode).name) # self.log.debug("[TNC] FIXED MODE:", mode=FREEDV_MODE(data_mode).name)
# we are doing a modulo check of transmission retries of the actual burst # we are doing a modulo check of transmission retries of the actual burst
@ -906,16 +955,17 @@ class DATA:
# Tempbuffer list for storing our data frames # Tempbuffer list for storing our data frames
tempbuffer = [] tempbuffer = []
# Append data frames with TX_N_FRAMES_PER_BURST to tempbuffer # Append data frames with n_frames_per_burst to tempbuffer
# TODO: this part needs a complete rewrite! # TODO: this part needs a complete rewrite!
# TX_N_FRAMES_PER_BURST = 1 is working # n_frames_per_burst = 1 is working
arqheader = bytearray() arqheader = bytearray()
# arqheader[:1] = bytes([FR_TYPE.BURST_01.value + i]) # arqheader[:1] = bytes([FR_TYPE.BURST_01.value + i])
arqheader[:1] = bytes([FR_TYPE.BURST_01.value]) arqheader[:1] = bytes([FR_TYPE.BURST_01.value])
arqheader[1:2] = bytes([TX_N_FRAMES_PER_BURST]) arqheader[1:2] = bytes([n_frames_per_burst])
arqheader[2:5] = static.DXCALLSIGN_CRC arqheader[2:3] = self.session_id
arqheader[5:8] = static.MYCALLSIGN_CRC # arqheader[2:5] = static.DXCALLSIGN_CRC
# arqheader[5:8] = static.MYCALLSIGN_CRC
bufferposition_end = bufferposition + payload_per_frame - len(arqheader) bufferposition_end = bufferposition + payload_per_frame - len(arqheader)
@ -939,21 +989,21 @@ class DATA:
self.log.info( self.log.info(
"[TNC] ARQ | TX | FRAMES", "[TNC] ARQ | TX | FRAMES",
mode=FREEDV_MODE(data_mode).name, mode=FREEDV_MODE(data_mode).name,
fpb=TX_N_FRAMES_PER_BURST, fpb=n_frames_per_burst,
retry=self.tx_n_retry_of_burst, retry=self.tx_n_retry_of_burst,
) )
for t_buf_item in tempbuffer: for t_buf_item in tempbuffer:
self.enqueue_frame_for_tx(t_buf_item, c2_mode=data_mode) self.enqueue_frame_for_tx([t_buf_item], c2_mode=data_mode)
# After transmission finished, wait for an ACK or RPT frame # After transmission finished, wait for an ACK or RPT frame
# burstacktimeout = time.time() + BURST_ACK_TIMEOUT_SECONDS + 100 # burstacktimeout = time.time() + self.burst_ack_timeout_seconds + 100
# while (not self.burst_ack and not self.burst_nack and # while (not self.burst_ack and not self.burst_nack and
# not self.rpt_request_received and not self.data_frame_ack_received and # not self.rpt_request_received and not self.data_frame_ack_received and
# time.time() < burstacktimeout and static.ARQ_STATE): # time.time() < burstacktimeout and static.ARQ_STATE):
# time.sleep(0.01) # time.sleep(0.01)
# burstacktimeout = time.time() + BURST_ACK_TIMEOUT_SECONDS + 100 # burstacktimeout = time.time() + self.burst_ack_timeout_seconds + 100
while static.ARQ_STATE and not ( while static.ARQ_STATE and not (
self.burst_ack self.burst_ack
or self.burst_nack or self.burst_nack
@ -1000,7 +1050,7 @@ class DATA:
self.log.debug( self.log.debug(
"[TNC] ATTEMPT:", "[TNC] ATTEMPT:",
retry=self.tx_n_retry_of_burst, retry=self.tx_n_retry_of_burst,
maxretries=TX_N_MAX_RETRIES_PER_BURST, maxretries=self.tx_n_max_retries_per_burst,
overflows=static.BUFFER_OVERFLOW_COUNTER, overflows=static.BUFFER_OVERFLOW_COUNTER,
) )
# End of FOR loop # End of FOR loop
@ -1022,7 +1072,7 @@ class DATA:
bytesperminute=static.ARQ_BYTES_PER_MINUTE, bytesperminute=static.ARQ_BYTES_PER_MINUTE,
) )
# Stay in the while loop until we receive a data_frame_ack. Otherwise # Stay in the while loop until we receive a data_frame_ack. Otherwise,
# the loop exits after sending the last frame only once and doesn't # the loop exits after sending the last frame only once and doesn't
# wait for an acknowledgement. # wait for an acknowledgement.
if self.data_frame_ack_received and bufferposition > len(data_out): if self.data_frame_ack_received and bufferposition > len(data_out):
@ -1076,7 +1126,7 @@ class DATA:
def burst_ack_nack_received(self, data_in: bytes) -> None: def burst_ack_nack_received(self, data_in: bytes) -> None:
""" """
Received a ACK/NACK for a transmitted frame, keep track and Received an ACK/NACK for a transmitted frame, keep track and
make adjustments to speed level if needed. make adjustments to speed level if needed.
Args: Args:
@ -1118,8 +1168,8 @@ class DATA:
# Update data_channel timestamp # Update data_channel timestamp
self.data_channel_last_received = int(time.time()) self.data_channel_last_received = int(time.time())
self.burst_ack_snr = int.from_bytes(bytes(data_in[7:8]), "big") self.burst_ack_snr = int.from_bytes(bytes(data_in[2:3]), "big")
self.speed_level = int.from_bytes(bytes(data_in[8:9]), "big") self.speed_level = int.from_bytes(bytes(data_in[3:4]), "big")
static.ARQ_SPEED_LEVEL = self.speed_level static.ARQ_SPEED_LEVEL = self.speed_level
self.log.debug( self.log.debug(
@ -1272,11 +1322,16 @@ class DATA:
self.IS_ARQ_SESSION_MASTER = True self.IS_ARQ_SESSION_MASTER = True
static.ARQ_SESSION_STATE = "connecting" static.ARQ_SESSION_STATE = "connecting"
connection_frame = bytearray(self.length_sig_frame) # create a random session id
self.session_id = randbytes(1)
print(self.session_id)
connection_frame = bytearray(self.length_sig0_frame)
connection_frame[:1] = bytes([FR_TYPE.ARQ_SESSION_OPEN.value]) connection_frame[:1] = bytes([FR_TYPE.ARQ_SESSION_OPEN.value])
connection_frame[1:4] = static.DXCALLSIGN_CRC connection_frame[1:2] = self.session_id
connection_frame[4:7] = static.MYCALLSIGN_CRC connection_frame[2:5] = static.DXCALLSIGN_CRC
connection_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign) connection_frame[5:8] = static.MYCALLSIGN_CRC
connection_frame[8:14] = helpers.callsign_to_bytes(self.mycallsign)
while not static.ARQ_SESSION: while not static.ARQ_SESSION:
time.sleep(0.01) time.sleep(0.01)
@ -1291,7 +1346,7 @@ class DATA:
state=static.ARQ_SESSION_STATE, state=static.ARQ_SESSION_STATE,
) )
self.enqueue_frame_for_tx(connection_frame) self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.datac0.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 `static.ARQ_SESSION`
# indicates we've received a positive response from the far station. # indicates we've received a positive response from the far station.
@ -1325,8 +1380,9 @@ class DATA:
# Update arq_session timestamp # Update arq_session timestamp
self.arq_session_last_received = int(time.time()) self.arq_session_last_received = int(time.time())
static.DXCALLSIGN_CRC = bytes(data_in[4:7]) self.session_id = bytes(data_in[1:2])
static.DXCALLSIGN = helpers.bytes_to_callsign(bytes(data_in[7:13])) static.DXCALLSIGN_CRC = bytes(data_in[5:8])
static.DXCALLSIGN = helpers.bytes_to_callsign(bytes(data_in[8:14]))
helpers.add_to_heard_stations( helpers.add_to_heard_stations(
static.DXCALLSIGN, static.DXCALLSIGN,
@ -1377,9 +1433,11 @@ class DATA:
self.IS_ARQ_SESSION_MASTER = False self.IS_ARQ_SESSION_MASTER = False
static.ARQ_SESSION = False static.ARQ_SESSION = False
self.arq_cleanup()
# we need to send disconnect frame before doing arq cleanup
# we would lose our session id then
self.send_disconnect_frame() self.send_disconnect_frame()
self.arq_cleanup()
def received_session_close(self, data_in: bytes): def received_session_close(self, data_in: bytes):
""" """
@ -1394,7 +1452,8 @@ class DATA:
# is intended for this station. # is intended for this station.
# Close the session if the CRC matches the remote station in static. # Close the session if the CRC matches the remote station in static.
_valid_crc, _ = helpers.check_callsign(static.DXCALLSIGN, bytes(data_in[4:7])) _valid_crc, _ = helpers.check_callsign(static.DXCALLSIGN, bytes(data_in[4:7]))
if _valid_crc: _valid_session = helpers.check_session_id(self.session_id, bytes(data_in[1:2]))
if _valid_crc or _valid_session:
static.ARQ_SESSION_STATE = "disconnected" static.ARQ_SESSION_STATE = "disconnected"
helpers.add_to_heard_stations( helpers.add_to_heard_stations(
static.DXCALLSIGN, static.DXCALLSIGN,
@ -1429,12 +1488,13 @@ class DATA:
# static.TNC_STATE = "BUSY" # static.TNC_STATE = "BUSY"
# static.ARQ_SESSION_STATE = "connected" # static.ARQ_SESSION_STATE = "connected"
connection_frame = bytearray(self.length_sig_frame) connection_frame = bytearray(self.length_sig0_frame)
connection_frame[:1] = bytes([FR_TYPE.ARQ_SESSION_HB.value]) connection_frame[:1] = bytes([FR_TYPE.ARQ_SESSION_HB.value])
connection_frame[1:4] = static.DXCALLSIGN_CRC connection_frame[1:2] = self.session_id
connection_frame[4:7] = static.MYCALLSIGN_CRC # connection_frame[1:4] = static.DXCALLSIGN_CRC
# connection_frame[4:7] = static.MYCALLSIGN_CRC
self.enqueue_frame_for_tx(connection_frame) self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.datac0.value, copies=1, repeat_delay=0)
def received_session_heartbeat(self, data_in: bytes) -> None: def received_session_heartbeat(self, data_in: bytes) -> None:
""" """
@ -1443,9 +1503,10 @@ class DATA:
data_in:bytes: data_in:bytes:
""" """
# Accept session data if the DXCALLSIGN_CRC matches the station in static. # Accept session data if the DXCALLSIGN_CRC matches the station in static or session id.
_valid_crc, _ = helpers.check_callsign(static.DXCALLSIGN, bytes(data_in[4:7])) _valid_crc, _ = helpers.check_callsign(static.DXCALLSIGN, bytes(data_in[4:7]))
if _valid_crc: _valid_session = helpers.check_session_id(self.session_id, bytes(data_in[1:2]))
if _valid_crc or _valid_session:
self.log.debug("[TNC] Received session heartbeat") self.log.debug("[TNC] Received session heartbeat")
helpers.add_to_heard_stations( helpers.add_to_heard_stations(
static.DXCALLSIGN, static.DXCALLSIGN,
@ -1463,8 +1524,19 @@ class DATA:
# Update the timeout timestamps # Update the timeout timestamps
self.arq_session_last_received = int(time.time()) self.arq_session_last_received = int(time.time())
self.data_channel_last_received = int(time.time()) self.data_channel_last_received = int(time.time())
# transmit session heartbeat only
if not self.IS_ARQ_SESSION_MASTER and not self.arq_file_transfer: # -> if not session master
# --> this will be triggered by heartbeat watchdog
# -> if not during a file transfer
# -> if ARQ_SESSION_STATE != disconnecting, disconnected, failed
# --> to avoid heartbeat toggle loops while disconnecting
if (
not self.IS_ARQ_SESSION_MASTER
and not self.arq_file_transfer
and static.ARQ_SESSION_STATE != 'disconnecting'
and static.ARQ_SESSION_STATE != 'disconnected'
and static.ARQ_SESSION_STATE != 'failed'
):
self.transmit_session_heartbeat() self.transmit_session_heartbeat()
########################################################################################################## ##########################################################################################################
@ -1485,6 +1557,8 @@ class DATA:
data_out:bytes: data_out:bytes:
mode:int: mode:int:
n_frames_per_burst:int: n_frames_per_burst:int:
transmission_uuid:str:
mycallsign:bytes:
Returns: Returns:
True if the data session was opened and the data was sent True if the data session was opened and the data was sent
@ -1499,12 +1573,13 @@ class DATA:
self.transmission_uuid = transmission_uuid self.transmission_uuid = transmission_uuid
# wait a moment for the case, a heartbeat is already on the way back to us # 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: if static.ARQ_SESSION:
time.sleep(0.5) time.sleep(2)
self.datachannel_timeout = False self.datachannel_timeout = False
# we need to compress data for gettin a compression factor. # 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 # so we are compressing twice. This is not that nice and maybe there is another way
# for calculating transmission statistics # for calculating transmission statistics
# static.ARQ_COMPRESSION_FACTOR = len(data_out) / len(zlib.compress(data_out)) # static.ARQ_COMPRESSION_FACTOR = len(data_out) / len(zlib.compress(data_out))
@ -1530,6 +1605,7 @@ class DATA:
Args: Args:
mode:int: mode:int:
n_frames_per_burst:int: n_frames_per_burst:int:
mycallsign:bytes:
Returns: Returns:
True if the data channel was opened successfully True if the data channel was opened successfully
@ -1537,6 +1613,11 @@ class DATA:
""" """
self.is_IRS = False self.is_IRS = False
# init a new random session id if we are not in an arq session
if not static.ARQ_SESSION:
self.session_id = randbytes(1)
print(self.session_id)
# Update data_channel timestamp # Update data_channel timestamp
self.data_channel_last_received = int(time.time()) self.data_channel_last_received = int(time.time())
@ -1548,12 +1629,13 @@ class DATA:
frametype = bytes([FR_TYPE.ARQ_DC_OPEN_W.value]) frametype = bytes([FR_TYPE.ARQ_DC_OPEN_W.value])
self.log.debug("[TNC] Requesting high bandwidth mode") self.log.debug("[TNC] Requesting high bandwidth mode")
connection_frame = bytearray(self.length_sig_frame) connection_frame = bytearray(self.length_sig0_frame)
connection_frame[:1] = frametype connection_frame[:1] = frametype
connection_frame[1:4] = static.DXCALLSIGN_CRC connection_frame[1:4] = static.DXCALLSIGN_CRC
connection_frame[4:7] = static.MYCALLSIGN_CRC connection_frame[4:7] = static.MYCALLSIGN_CRC
connection_frame[7:13] = helpers.callsign_to_bytes(mycallsign) connection_frame[7:13] = helpers.callsign_to_bytes(mycallsign)
connection_frame[13:14] = bytes([n_frames_per_burst]) # connection_frame[13:14] = bytes([n_frames_per_burst])
connection_frame[13:14] = self.session_id
while not static.ARQ_STATE: while not static.ARQ_STATE:
time.sleep(0.01) time.sleep(0.01)
@ -1574,7 +1656,7 @@ class DATA:
attempt=f"{str(attempt + 1)}/{str(self.data_channel_max_retries)}", attempt=f"{str(attempt + 1)}/{str(self.data_channel_max_retries)}",
) )
self.enqueue_frame_for_tx(connection_frame) self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.datac0.value, copies=1, repeat_delay=0)
timeout = time.time() + 4 timeout = time.time() + 4
while time.time() < timeout: while time.time() < timeout:
@ -1607,19 +1689,20 @@ class DATA:
+ "]" + "]"
) )
self.datachannel_timeout = True self.datachannel_timeout = True
self.arq_cleanup()
# Attempt to cleanup the far-side, if it received the # Attempt to clean up the far-side, if it received the
# open_session frame and can still hear us. # open_session frame and can still hear us.
self.close_session() self.close_session()
self.arq_cleanup()
return False return False
# Shouldn't get here.. # Shouldn't get here...
return True return True
def arq_received_data_channel_opener(self, data_in: bytes): def arq_received_data_channel_opener(self, data_in: bytes):
""" """
Received request to open data channel framt Received request to open data channel frame
Args: Args:
data_in:bytes: data_in:bytes:
@ -1699,7 +1782,7 @@ class DATA:
) )
# Update modes we are listening to # Update modes we are listening to
self.set_listening_modes(self.mode_list[self.speed_level]) self.set_listening_modes(True, True, self.mode_list[self.speed_level])
helpers.add_to_heard_stations( helpers.add_to_heard_stations(
static.DXCALLSIGN, static.DXCALLSIGN,
@ -1710,6 +1793,9 @@ class DATA:
static.HAMLIB_FREQUENCY, static.HAMLIB_FREQUENCY,
) )
self.session_id = data_in[13:14]
print(self.session_id)
# check if callsign ssid override # check if callsign ssid override
_, mycallsign = helpers.check_callsign(self.mycallsign, data_in[1:4]) _, mycallsign = helpers.check_callsign(self.mycallsign, data_in[1:4])
@ -1738,16 +1824,17 @@ class DATA:
frametype = bytes([FR_TYPE.ARQ_DC_OPEN_ACK_W.value]) frametype = bytes([FR_TYPE.ARQ_DC_OPEN_ACK_W.value])
self.log.debug("[TNC] Responding with high bandwidth mode") self.log.debug("[TNC] Responding with high bandwidth mode")
connection_frame = bytearray(self.length_sig_frame) connection_frame = bytearray(self.length_sig0_frame)
connection_frame[:1] = frametype connection_frame[:1] = frametype
connection_frame[1:4] = static.DXCALLSIGN_CRC connection_frame[1:2] = self.session_id
connection_frame[4:7] = static.MYCALLSIGN_CRC # connection_frame[1:4] = static.DXCALLSIGN_CRC
# connection_frame[4:7] = static.MYCALLSIGN_CRC
connection_frame[8:9] = bytes([self.speed_level]) connection_frame[8:9] = bytes([self.speed_level])
# For checking protocol version on the receiving side # For checking protocol version on the receiving side
connection_frame[13:14] = bytes([static.ARQ_PROTOCOL_VERSION]) connection_frame[13:14] = bytes([static.ARQ_PROTOCOL_VERSION])
self.enqueue_frame_for_tx(connection_frame) self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.datac0.value, copies=1, repeat_delay=0)
self.log.info( self.log.info(
"[TNC] ARQ | DATA | RX | [" "[TNC] ARQ | DATA | RX | ["
@ -1860,7 +1947,7 @@ class DATA:
+ "]" + "]"
) )
ping_frame = bytearray(self.length_sig_frame) ping_frame = bytearray(self.length_sig0_frame)
ping_frame[:1] = bytes([FR_TYPE.PING.value]) ping_frame[:1] = bytes([FR_TYPE.PING.value])
ping_frame[1:4] = static.DXCALLSIGN_CRC ping_frame[1:4] = static.DXCALLSIGN_CRC
ping_frame[4:7] = static.MYCALLSIGN_CRC ping_frame[4:7] = static.MYCALLSIGN_CRC
@ -1868,9 +1955,9 @@ class DATA:
self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK) self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK)
if static.ENABLE_FSK: if static.ENABLE_FSK:
self.enqueue_frame_for_tx(ping_frame, c2_mode=FREEDV_MODE.fsk_ldpc_0.value) self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value)
else: 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.datac0.value)
def received_ping(self, data_in: bytes) -> None: def received_ping(self, data_in: bytes) -> None:
""" """
@ -1919,7 +2006,7 @@ class DATA:
snr=static.SNR, snr=static.SNR,
) )
ping_frame = bytearray(self.length_sig_frame) ping_frame = bytearray(self.length_sig0_frame)
ping_frame[:1] = bytes([FR_TYPE.PING_ACK.value]) ping_frame[:1] = bytes([FR_TYPE.PING_ACK.value])
ping_frame[1:4] = static.DXCALLSIGN_CRC ping_frame[1:4] = static.DXCALLSIGN_CRC
ping_frame[4:7] = static.MYCALLSIGN_CRC ping_frame[4:7] = static.MYCALLSIGN_CRC
@ -1927,9 +2014,9 @@ class DATA:
self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK) self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK)
if static.ENABLE_FSK: if static.ENABLE_FSK:
self.enqueue_frame_for_tx(ping_frame, c2_mode=FREEDV_MODE.fsk_ldpc_0.value) self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value)
else: 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.datac0.value)
def received_ping_ack(self, data_in: bytes) -> None: def received_ping_ack(self, data_in: bytes) -> None:
""" """
@ -1976,13 +2063,14 @@ class DATA:
Force a stop of the running transmission Force a stop of the running transmission
""" """
self.log.warning("[TNC] Stopping transmission!") self.log.warning("[TNC] Stopping transmission!")
stop_frame = bytearray(self.length_sig_frame) stop_frame = bytearray(self.length_sig0_frame)
stop_frame[:1] = bytes([FR_TYPE.ARQ_STOP.value]) stop_frame[:1] = bytes([FR_TYPE.ARQ_STOP.value])
stop_frame[1:4] = static.DXCALLSIGN_CRC stop_frame[1:4] = static.DXCALLSIGN_CRC
stop_frame[4:7] = static.MYCALLSIGN_CRC stop_frame[4:7] = static.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) 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, copies=2, repeat_delay=250)
static.TNC_STATE = "IDLE" static.TNC_STATE = "IDLE"
static.ARQ_STATE = False static.ARQ_STATE = False
@ -2041,7 +2129,7 @@ class DATA:
"[TNC] Sending beacon!", interval=self.beacon_interval "[TNC] Sending beacon!", interval=self.beacon_interval
) )
beacon_frame = bytearray(self.length_sig_frame) beacon_frame = bytearray(self.length_sig0_frame)
beacon_frame[:1] = bytes([FR_TYPE.BEACON.value]) beacon_frame[:1] = bytes([FR_TYPE.BEACON.value])
beacon_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign) beacon_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign)
beacon_frame[9:13] = static.MYGRID[:4] beacon_frame[9:13] = static.MYGRID[:4]
@ -2049,11 +2137,12 @@ class DATA:
if static.ENABLE_FSK: if static.ENABLE_FSK:
self.enqueue_frame_for_tx( self.enqueue_frame_for_tx(
beacon_frame, [beacon_frame],
c2_mode=FREEDV_MODE.fsk_ldpc_0.value, c2_mode=FREEDV_MODE.fsk_ldpc_0.value,
) )
else: else:
self.enqueue_frame_for_tx(beacon_frame) self.enqueue_frame_for_tx([beacon_frame], c2_mode=FREEDV_MODE.datac0.value, copies=1,
repeat_delay=0)
interval_timer = time.time() + self.beacon_interval interval_timer = time.time() + self.beacon_interval
while ( while (
@ -2109,7 +2198,7 @@ class DATA:
""" """
Transmit a CQ Transmit a CQ
Args: Args:
Nothing self
Returns: Returns:
Nothing Nothing
@ -2119,7 +2208,7 @@ class DATA:
freedata="tnc-message", freedata="tnc-message",
cq="transmitting", cq="transmitting",
) )
cq_frame = bytearray(self.length_sig_frame) cq_frame = bytearray(self.length_sig0_frame)
cq_frame[:1] = bytes([FR_TYPE.CQ.value]) cq_frame[:1] = bytes([FR_TYPE.CQ.value])
cq_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign) 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(static.MYGRID.decode("UTF-8"))
@ -2128,9 +2217,9 @@ class DATA:
self.log.debug("[TNC] CQ Frame:", data=[cq_frame]) self.log.debug("[TNC] CQ Frame:", data=[cq_frame])
if static.ENABLE_FSK: if static.ENABLE_FSK:
self.enqueue_frame_for_tx(cq_frame, c2_mode=FREEDV_MODE.fsk_ldpc_0.value) self.enqueue_frame_for_tx([cq_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value)
else: else:
self.enqueue_frame_for_tx(cq_frame) self.enqueue_frame_for_tx([cq_frame], c2_mode=FREEDV_MODE.datac0.value, copies=1, repeat_delay=0)
def received_cq(self, data_in: bytes) -> None: def received_cq(self, data_in: bytes) -> None:
""" """
@ -2190,7 +2279,7 @@ class DATA:
) )
self.log.info("[TNC] Sending QRV!") self.log.info("[TNC] Sending QRV!")
qrv_frame = bytearray(self.length_sig_frame) qrv_frame = bytearray(self.length_sig0_frame)
qrv_frame[:1] = bytes([FR_TYPE.QRV.value]) qrv_frame[:1] = bytes([FR_TYPE.QRV.value])
qrv_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign) qrv_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign)
qrv_frame[7:11] = helpers.encode_grid(static.MYGRID.decode("UTF-8")) qrv_frame[7:11] = helpers.encode_grid(static.MYGRID.decode("UTF-8"))
@ -2198,9 +2287,9 @@ class DATA:
self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK) self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK)
if static.ENABLE_FSK: if static.ENABLE_FSK:
self.enqueue_frame_for_tx(qrv_frame, c2_mode=FREEDV_MODE.fsk_ldpc_0.value) self.enqueue_frame_for_tx([qrv_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value)
else: else:
self.enqueue_frame_for_tx(qrv_frame) self.enqueue_frame_for_tx([qrv_frame], c2_mode=FREEDV_MODE.datac0.value, copies=1, repeat_delay=0)
def received_qrv(self, data_in: bytes) -> None: def received_qrv(self, data_in: bytes) -> None:
""" """
@ -2235,7 +2324,7 @@ class DATA:
static.HAMLIB_FREQUENCY, static.HAMLIB_FREQUENCY,
) )
# ------------ CALUCLATE TRANSFER RATES # ------------ CALCULATE TRANSFER RATES
def calculate_transfer_rate_rx( def calculate_transfer_rate_rx(
self, rx_start_of_transmission: float, receivedbytes: int self, rx_start_of_transmission: float, receivedbytes: int
) -> list: ) -> list:
@ -2258,7 +2347,7 @@ class DATA:
( (
receivedbytes receivedbytes
* static.ARQ_COMPRESSION_FACTOR * static.ARQ_COMPRESSION_FACTOR
/ (static.TOTAL_BYTES) / static.TOTAL_BYTES
) )
* 100 * 100
), ),
@ -2270,7 +2359,7 @@ class DATA:
if receivedbytes > 0: if receivedbytes > 0:
static.ARQ_BITS_PER_SECOND = int((receivedbytes * 8) / transmissiontime) static.ARQ_BITS_PER_SECOND = int((receivedbytes * 8) / transmissiontime)
static.ARQ_BYTES_PER_MINUTE = int( static.ARQ_BYTES_PER_MINUTE = int(
(receivedbytes) / (transmissiontime / 60) receivedbytes / (transmissiontime / 60)
) )
else: else:
@ -2324,7 +2413,7 @@ class DATA:
if sentbytes > 0: if sentbytes > 0:
static.ARQ_BITS_PER_SECOND = int((sentbytes * 8) / transmissiontime) static.ARQ_BITS_PER_SECOND = int((sentbytes * 8) / transmissiontime)
static.ARQ_BYTES_PER_MINUTE = int((sentbytes) / (transmissiontime / 60)) static.ARQ_BYTES_PER_MINUTE = int(sentbytes / (transmissiontime / 60))
else: else:
static.ARQ_BITS_PER_SECOND = 0 static.ARQ_BITS_PER_SECOND = 0
@ -2353,6 +2442,7 @@ class DATA:
self.log.debug("[TNC] arq_cleanup") self.log.debug("[TNC] arq_cleanup")
self.session_id = bytes(1)
self.rx_frame_bof_received = False self.rx_frame_bof_received = False
self.rx_frame_eof_received = False self.rx_frame_eof_received = False
self.burst_ack = False self.burst_ack = False
@ -2363,6 +2453,8 @@ class DATA:
self.burst_ack_snr = 255 self.burst_ack_snr = 255
# reset modem receiving state to reduce cpu load # reset modem receiving state to reduce cpu load
modem.RECEIVE_SIG0 = True
modem.RECEIVE_SIG1 = False
modem.RECEIVE_DATAC1 = False modem.RECEIVE_DATAC1 = False
modem.RECEIVE_DATAC3 = False modem.RECEIVE_DATAC3 = False
# modem.RECEIVE_FSK_LDPC_0 = False # modem.RECEIVE_FSK_LDPC_0 = False
@ -2403,15 +2495,20 @@ class DATA:
self.rpt_request_received = state self.rpt_request_received = state
self.data_frame_ack_received = state self.data_frame_ack_received = state
def set_listening_modes(self, mode: int) -> None: def set_listening_modes(self, enable_sig0: bool, enable_sig1: bool, mode: int) -> None:
""" """
Function for setting the data modes we are listening to for saving cpu power Function for setting the data modes we are listening to for saving cpu power
Args: Args:
enable_sig0:int: Enable/Disable signalling mode 0
enable_sig1:int: Enable/Disable signalling mode 1
mode:int: Codec2 mode to listen for mode:int: Codec2 mode to listen for
""" """
# set modes we want to listen to # set modes we want to listen to
modem.RECEIVE_SIG0 = enable_sig0
modem.RECEIVE_SIG1 = enable_sig1
if mode == FREEDV_MODE.datac1.value: if mode == FREEDV_MODE.datac1.value:
modem.RECEIVE_DATAC1 = True modem.RECEIVE_DATAC1 = True
self.log.debug("[TNC] Changing listening data mode", mode="datac1") self.log.debug("[TNC] Changing listening data mode", mode="datac1")
@ -2476,7 +2573,7 @@ class DATA:
static.ARQ_SPEED_LEVEL = self.speed_level static.ARQ_SPEED_LEVEL = self.speed_level
# Update modes we are listening to # Update modes we are listening to
self.set_listening_modes(self.mode_list[self.speed_level]) self.set_listening_modes(True, True, self.mode_list[self.speed_level])
# Why not pass `snr` or `static.SNR`? # Why not pass `snr` or `static.SNR`?
self.send_burst_nack_frame_watchdog(0) self.send_burst_nack_frame_watchdog(0)
@ -2504,7 +2601,10 @@ class DATA:
self.data_channel_last_received + self.transmission_timeout self.data_channel_last_received + self.transmission_timeout
> time.time() > time.time()
): ):
time.sleep(0.01) time.sleep(5)
timeleft = (self.data_channel_last_received + self.transmission_timeout) - time.time()
self.log.debug("Time left until timeout", seconds=timeleft)
# print(self.data_channel_last_received + self.transmission_timeout - time.time()) # print(self.data_channel_last_received + self.transmission_timeout - time.time())
# pass # pass
else: else:
@ -2559,18 +2659,21 @@ class DATA:
""" """
while True: while True:
time.sleep(0.01) time.sleep(0.01)
if ( # additional check for smoother stopping if heartbeat transmission
static.ARQ_SESSION while not self.arq_file_transfer:
and self.IS_ARQ_SESSION_MASTER time.sleep(0.01)
and static.ARQ_SESSION_STATE == "connected" if (
and not self.arq_file_transfer static.ARQ_SESSION
): and self.IS_ARQ_SESSION_MASTER
time.sleep(1) and static.ARQ_SESSION_STATE == "connected"
self.transmit_session_heartbeat() # and not self.arq_file_transfer
time.sleep(2) ):
time.sleep(1)
self.transmit_session_heartbeat()
time.sleep(2)
def send_test_frame(self) -> None: def send_test_frame(self) -> None:
"""Send an empty test frame""" """Send an empty test frame"""
self.enqueue_frame_for_tx( self.enqueue_frame_for_tx(
frame_to_tx=bytearray(126), c2_mode=FREEDV_MODE.datac3.value frame_to_tx=[bytearray(126)], c2_mode=FREEDV_MODE.datac3.value
) )

View file

@ -316,6 +316,21 @@ def check_callsign(callsign: bytes, crc_to_check: bytes):
return [False, ""] return [False, ""]
def check_session_id(id: bytes, id_to_check: bytes):
"""
Funktion to check if we received the correct session id
Args:
id: our own session id
id_to_check: The session id byte we want to check
Returns:
True
False
"""
log.debug("[HLP] check_sessionid: Checking:", ownid=id, check=id_to_check)
return id == id_to_check
def encode_grid(grid): def encode_grid(grid):
""" """

View file

@ -32,6 +32,8 @@ TXCHANNEL = ""
static.TRANSMITTING = False static.TRANSMITTING = False
# Receive only specific modes to reduce CPU load # Receive only specific modes to reduce CPU load
RECEIVE_SIG0 = True
RECEIVE_SIG1 = False
RECEIVE_DATAC1 = False RECEIVE_DATAC1 = False
RECEIVE_DATAC3 = False RECEIVE_DATAC3 = False
RECEIVE_FSK_LDPC_1 = False RECEIVE_FSK_LDPC_1 = False
@ -81,110 +83,63 @@ class RF:
self.fft_data = bytes() self.fft_data = bytes()
# Open codec2 instances # Open codec2 instances
# Datac0 - control frames
self.datac0_freedv = ctypes.cast(
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC0), ctypes.c_void_p
)
self.c_lib.freedv_set_tuning_range(
self.datac0_freedv,
ctypes.c_float(static.TUNING_RANGE_FMIN),
ctypes.c_float(static.TUNING_RANGE_FMAX),
)
self.datac0_bytes_per_frame = int(
codec2.api.freedv_get_bits_per_modem_frame(self.datac0_freedv) / 8
)
self.datac0_bytes_out = ctypes.create_string_buffer(self.datac0_bytes_per_frame)
codec2.api.freedv_set_frames_per_burst(self.datac0_freedv, 1)
self.datac0_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX)
# Additional Datac0-specific information - these are not referenced anywhere else. # DATAC0
# self.datac0_payload_per_frame = self.datac0_bytes_per_frame - 2 # SIGNALLING MODE 0 - Used for Connecting - Payload 14 Bytes
# self.datac0_n_nom_modem_samples = self.c_lib.freedv_get_n_nom_modem_samples( self.sig0_datac0_freedv, \
# self.datac0_freedv self.sig0_datac0_bytes_per_frame, \
# ) self.sig0_datac0_bytes_out, \
# self.datac0_n_tx_modem_samples = self.c_lib.freedv_get_n_tx_modem_samples( self.sig0_datac0_buffer, \
# self.datac0_freedv self.sig0_datac0_nin = \
# ) self.init_codec2_mode(codec2.api.FREEDV_MODE_DATAC0, None)
# self.datac0_n_tx_preamble_modem_samples = (
# self.c_lib.freedv_get_n_tx_preamble_modem_samples(self.datac0_freedv)
# )
# self.datac0_n_tx_postamble_modem_samples = (
# self.c_lib.freedv_get_n_tx_postamble_modem_samples(self.datac0_freedv)
# )
# Datac1 - higher-bandwidth data frames # DATAC0
self.datac1_freedv = ctypes.cast( # SIGNALLING MODE 1 - Used for ACK/NACK - Payload 5 Bytes
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), ctypes.c_void_p self.sig1_datac0_freedv, \
) self.sig1_datac0_bytes_per_frame, \
self.c_lib.freedv_set_tuning_range( self.sig1_datac0_bytes_out, \
self.datac1_freedv, self.sig1_datac0_buffer, \
ctypes.c_float(static.TUNING_RANGE_FMIN), self.sig1_datac0_nin = \
ctypes.c_float(static.TUNING_RANGE_FMAX), self.init_codec2_mode(codec2.api.FREEDV_MODE_DATAC0, None)
)
self.datac1_bytes_per_frame = int(
codec2.api.freedv_get_bits_per_modem_frame(self.datac1_freedv) / 8
)
self.datac1_bytes_out = ctypes.create_string_buffer(self.datac1_bytes_per_frame)
codec2.api.freedv_set_frames_per_burst(self.datac1_freedv, 1)
self.datac1_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX)
# Datac3 - lower-bandwidth data frames
self.datac3_freedv = ctypes.cast(
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC3), ctypes.c_void_p
)
self.c_lib.freedv_set_tuning_range(
self.datac3_freedv,
ctypes.c_float(static.TUNING_RANGE_FMIN),
ctypes.c_float(static.TUNING_RANGE_FMAX),
)
self.datac3_bytes_per_frame = int(
codec2.api.freedv_get_bits_per_modem_frame(self.datac3_freedv) / 8
)
self.datac3_bytes_out = ctypes.create_string_buffer(self.datac3_bytes_per_frame)
codec2.api.freedv_set_frames_per_burst(self.datac3_freedv, 1)
self.datac3_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX)
# FSK Long-distance Parity Code 0 - data frames # DATAC1
self.fsk_ldpc_freedv_0 = ctypes.cast( self.dat0_datac1_freedv, \
codec2.api.freedv_open_advanced( self.dat0_datac1_bytes_per_frame, \
self.dat0_datac1_bytes_out, \
self.dat0_datac1_buffer, \
self.dat0_datac1_nin = \
self.init_codec2_mode(codec2.api.FREEDV_MODE_DATAC1, None)
# DATAC3
self.dat0_datac3_freedv, \
self.dat0_datac3_bytes_per_frame, \
self.dat0_datac3_bytes_out, \
self.dat0_datac3_buffer, \
self.dat0_datac3_nin = \
self.init_codec2_mode(codec2.api.FREEDV_MODE_DATAC3, None)
# FSK LDPC - 0
self.fsk_ldpc_freedv_0, \
self.fsk_ldpc_bytes_per_frame_0, \
self.fsk_ldpc_bytes_out_0, \
self.fsk_ldpc_buffer_0, \
self.fsk_ldpc_nin_0 = \
self.init_codec2_mode(
codec2.api.FREEDV_MODE_FSK_LDPC, codec2.api.FREEDV_MODE_FSK_LDPC,
ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV), codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV
), )
ctypes.c_void_p,
)
self.fsk_ldpc_bytes_per_frame_0 = int(
codec2.api.freedv_get_bits_per_modem_frame(self.fsk_ldpc_freedv_0) / 8
)
self.fsk_ldpc_bytes_out_0 = ctypes.create_string_buffer(
self.fsk_ldpc_bytes_per_frame_0
)
# codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, 1)
self.fsk_ldpc_buffer_0 = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER_RX)
# FSK Long-distance Parity Code 1 - data frames # FSK LDPC - 1
self.fsk_ldpc_freedv_1 = ctypes.cast( self.fsk_ldpc_freedv_1, \
codec2.api.freedv_open_advanced( self.fsk_ldpc_bytes_per_frame_1, \
self.fsk_ldpc_bytes_out_1, \
self.fsk_ldpc_buffer_1, \
self.fsk_ldpc_nin_1 = \
self.init_codec2_mode(
codec2.api.FREEDV_MODE_FSK_LDPC, codec2.api.FREEDV_MODE_FSK_LDPC,
ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV), codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV
), )
ctypes.c_void_p,
)
self.fsk_ldpc_bytes_per_frame_1 = int(
codec2.api.freedv_get_bits_per_modem_frame(self.fsk_ldpc_freedv_1) / 8
)
self.fsk_ldpc_bytes_out_1 = ctypes.create_string_buffer(
self.fsk_ldpc_bytes_per_frame_1
)
# codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, 1)
self.fsk_ldpc_buffer_1 = codec2.audio_buffer(self.AUDIO_FRAMES_PER_BUFFER_RX)
# initial nin values
self.datac0_nin = codec2.api.freedv_nin(self.datac0_freedv)
self.datac1_nin = codec2.api.freedv_nin(self.datac1_freedv)
self.datac3_nin = codec2.api.freedv_nin(self.datac3_freedv)
self.fsk_ldpc_nin_0 = codec2.api.freedv_nin(self.fsk_ldpc_freedv_0)
self.fsk_ldpc_nin_1 = codec2.api.freedv_nin(self.fsk_ldpc_freedv_1)
# self.log.debug("[MDM] RF: ",datac0_nin=self.datac0_nin)
# --------------------------------------------CREATE PYAUDIO INSTANCE # --------------------------------------------CREATE PYAUDIO INSTANCE
if not TESTMODE: if not TESTMODE:
@ -242,6 +197,7 @@ class RF:
# --------------------------------------------INIT AND OPEN HAMLIB # --------------------------------------------INIT AND OPEN HAMLIB
# Check how we want to control the radio # Check how we want to control the radio
# TODO: deprecated feature - we can remove this possibly
if static.HAMLIB_RADIOCONTROL == "direct": if static.HAMLIB_RADIOCONTROL == "direct":
import rig import rig
elif static.HAMLIB_RADIOCONTROL == "rigctl": elif static.HAMLIB_RADIOCONTROL == "rigctl":
@ -272,20 +228,25 @@ class RF:
) )
fft_thread.start() fft_thread.start()
audio_thread_datac0 = threading.Thread( audio_thread_sig0_datac0 = threading.Thread(
target=self.audio_datac0, name="AUDIO_THREAD DATAC0", daemon=True target=self.audio_sig0_datac0, name="AUDIO_THREAD DATAC0 - 0", daemon=True
) )
audio_thread_datac0.start() audio_thread_sig0_datac0.start()
audio_thread_datac1 = threading.Thread( audio_thread_sig1_datac0 = threading.Thread(
target=self.audio_datac1, name="AUDIO_THREAD DATAC1", daemon=True target=self.audio_sig1_datac0, name="AUDIO_THREAD DATAC0 - 1", daemon=True
) )
audio_thread_datac1.start() audio_thread_sig1_datac0.start()
audio_thread_datac3 = threading.Thread( audio_thread_dat0_datac1 = threading.Thread(
target=self.audio_datac3, name="AUDIO_THREAD DATAC3", daemon=True target=self.audio_dat0_datac1, name="AUDIO_THREAD DATAC1", daemon=True
) )
audio_thread_datac3.start() audio_thread_dat0_datac1.start()
audio_thread_dat0_datac3 = threading.Thread(
target=self.audio_dat0_datac3, name="AUDIO_THREAD DATAC3", daemon=True
)
audio_thread_dat0_datac3.start()
if static.ENABLE_FSK: if static.ENABLE_FSK:
audio_thread_fsk_ldpc0 = threading.Thread( audio_thread_fsk_ldpc0 = threading.Thread(
@ -335,9 +296,10 @@ class RF:
length_x = len(x) length_x = len(x)
for data_buffer, receive in [ for data_buffer, receive in [
(self.datac0_buffer, True), (self.sig0_datac0_buffer, RECEIVE_SIG0),
(self.datac1_buffer, RECEIVE_DATAC1), (self.sig1_datac0_buffer, RECEIVE_SIG1),
(self.datac3_buffer, RECEIVE_DATAC3), (self.dat0_datac1_buffer, RECEIVE_DATAC1),
(self.dat0_datac3_buffer, RECEIVE_DATAC3),
# Not enabled yet. # Not enabled yet.
# (self.fsk_ldpc_buffer_0, static.ENABLE_FSK), # (self.fsk_ldpc_buffer_0, static.ENABLE_FSK),
# (self.fsk_ldpc_buffer_1, static.ENABLE_FSK), # (self.fsk_ldpc_buffer_1, static.ENABLE_FSK),
@ -391,11 +353,12 @@ class RF:
# Avoid buffer overflow by filling only if buffer for # Avoid buffer overflow by filling only if buffer for
# selected datachannel mode is not full # selected datachannel mode is not full
for audiobuffer, receive, index in [ for audiobuffer, receive, index in [
(self.datac0_buffer, True, 0), (self.sig0_datac0_buffer, RECEIVE_SIG0, 0),
(self.datac1_buffer, RECEIVE_DATAC1, 1), (self.sig1_datac0_buffer, RECEIVE_SIG1, 1),
(self.datac3_buffer, RECEIVE_DATAC3, 2), (self.dat0_datac1_buffer, RECEIVE_DATAC1, 2),
(self.fsk_ldpc_buffer_0, static.ENABLE_FSK, 3), (self.dat0_datac3_buffer, RECEIVE_DATAC3, 3),
(self.fsk_ldpc_buffer_1, static.ENABLE_FSK, 4), (self.fsk_ldpc_buffer_0, static.ENABLE_FSK, 4),
(self.fsk_ldpc_buffer_1, static.ENABLE_FSK, 5),
]: ]:
if audiobuffer.nbuffer + length_x > audiobuffer.size: if audiobuffer.nbuffer + length_x > audiobuffer.size:
static.BUFFER_OVERFLOW_COUNTER[index] += 1 static.BUFFER_OVERFLOW_COUNTER[index] += 1
@ -618,38 +581,119 @@ class RF:
"[MDM] [demod_audio] Pushing received data to received_queue" "[MDM] [demod_audio] Pushing received data to received_queue"
) )
self.modem_received_queue.put([bytes_out, freedv, bytes_per_frame]) self.modem_received_queue.put([bytes_out, freedv, bytes_per_frame])
# self.get_scatter(freedv) self.get_scatter(freedv)
self.calculate_snr(freedv) self.calculate_snr(freedv)
return nin return nin
def audio_datac0(self) -> None: def init_codec2_mode(self, mode, adv):
"""Receive data encoded with datac0""" """
self.datac0_nin = self.demodulate_audio( Init codec2 and return some important parameters
self.datac0_buffer,
self.datac0_nin, Args:
self.datac0_freedv, self:
self.datac0_bytes_out, mode:
self.datac0_bytes_per_frame, adv:
Returns:
c2instance, bytes_per_frame, bytes_out, audio_buffer, nin
"""
if adv:
# FSK Long-distance Parity Code 1 - data frames
c2instance = ctypes.cast(
codec2.api.freedv_open_advanced(
codec2.api.FREEDV_MODE_FSK_LDPC,
ctypes.byref(adv),
),
ctypes.c_void_p,
)
else:
# create codec2 instance
c2instance = ctypes.cast(
codec2.api.freedv_open(mode), ctypes.c_void_p
)
# set tuning range
self.c_lib.freedv_set_tuning_range(
c2instance,
ctypes.c_float(static.TUNING_RANGE_FMIN),
ctypes.c_float(static.TUNING_RANGE_FMAX),
) )
def audio_datac1(self) -> None: # get bytes per frame
bytes_per_frame = int(
codec2.api.freedv_get_bits_per_modem_frame(c2instance) / 8
)
# create byte out buffer
bytes_out = ctypes.create_string_buffer(bytes_per_frame)
# set initial frames per burst
codec2.api.freedv_set_frames_per_burst(c2instance, 1)
# init audio buffer
audio_buffer = codec2.audio_buffer(2 * self.AUDIO_FRAMES_PER_BUFFER_RX)
# get initial nin
nin = codec2.api.freedv_nin(c2instance)
# 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_freedv
# )
# self.sig0_datac0_n_tx_modem_samples = self.c_lib.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)
# )
# self.sig0_datac0_n_tx_postamble_modem_samples = (
# self.c_lib.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,
)
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,
)
def audio_dat0_datac1(self) -> None:
"""Receive data encoded with datac1""" """Receive data encoded with datac1"""
self.datac1_nin = self.demodulate_audio( self.dat0_datac1_nin = self.demodulate_audio(
self.datac1_buffer, self.dat0_datac1_buffer,
self.datac1_nin, self.dat0_datac1_nin,
self.datac1_freedv, self.dat0_datac1_freedv,
self.datac1_bytes_out, self.dat0_datac1_bytes_out,
self.datac1_bytes_per_frame, self.dat0_datac1_bytes_per_frame,
) )
def audio_datac3(self) -> None: def audio_dat0_datac3(self) -> None:
"""Receive data encoded with datac3""" """Receive data encoded with datac3"""
self.datac3_nin = self.demodulate_audio( self.dat0_datac3_nin = self.demodulate_audio(
self.datac3_buffer, self.dat0_datac3_buffer,
self.datac3_nin, self.dat0_datac3_nin,
self.datac3_freedv, self.dat0_datac3_freedv,
self.datac3_bytes_out, self.dat0_datac3_bytes_out,
self.datac3_bytes_per_frame, self.dat0_datac3_bytes_per_frame,
) )
def audio_fsk_ldpc_0(self) -> None: def audio_fsk_ldpc_0(self) -> None:
@ -705,7 +749,6 @@ class RF:
:rtype: float :rtype: float
""" """
modemStats = codec2.MODEMSTATS() modemStats = codec2.MODEMSTATS()
self.c_lib.freedv_get_modem_extended_stats.restype = None
self.c_lib.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats)) self.c_lib.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats))
offset = round(modemStats.foff) * (-1) offset = round(modemStats.foff) * (-1)
static.FREQ_OFFSET = offset static.FREQ_OFFSET = offset
@ -723,28 +766,27 @@ class RF:
return return
modemStats = codec2.MODEMSTATS() modemStats = codec2.MODEMSTATS()
self.c_lib.freedv_get_modem_extended_stats.restype = None ctypes.cast(
self.c_lib.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats)) self.c_lib.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats)),
ctypes.c_void_p,
)
scatterdata = [] scatterdata = []
scatterdata_small = []
for i in range(codec2.MODEM_STATS_NC_MAX): for i in range(codec2.MODEM_STATS_NC_MAX):
for j in range(codec2.MODEM_STATS_NR_MAX): for j in range(1, codec2.MODEM_STATS_NR_MAX, 2):
# check if odd or not to get every 2nd item for x # print(f"{modemStats.rx_symbols[i][j]} - {modemStats.rx_symbols[i][j]}")
if (j % 2) == 0: xsymbols = round(modemStats.rx_symbols[i][j - 1] // 1000)
xsymbols = round(modemStats.rx_symbols[i][j] / 1000) ysymbols = round(modemStats.rx_symbols[i][j] // 1000)
ysymbols = round(modemStats.rx_symbols[i][j + 1] / 1000) if xsymbols != 0.0 and ysymbols != 0.0:
# check if value 0.0 or has real data scatterdata.append({"x": str(xsymbols), "y": str(ysymbols)})
if xsymbols != 0.0 and ysymbols != 0.0:
scatterdata.append({"x": xsymbols, "y": ysymbols})
# Send all the data if we have too-few samples, otherwise send a sampling # Send all the data if we have too-few samples, otherwise send a sampling
if 150 > len(scatterdata) > 0: if 150 > len(scatterdata) > 0:
static.SCATTER = scatterdata static.SCATTER = scatterdata
else: else:
# only take every tenth data point # only take every tenth data point
scatterdata_small = scatterdata[::10] static.SCATTER = scatterdata[::10]
static.SCATTER = scatterdata_small
def calculate_snr(self, freedv: ctypes.c_void_p) -> float: def calculate_snr(self, freedv: ctypes.c_void_p) -> float:
""" """
@ -801,6 +843,9 @@ class RF:
# Initialize channel_busy_delay counter # Initialize channel_busy_delay counter
channel_busy_delay = 0 channel_busy_delay = 0
# Initialize rms counter
rms_counter = 0
while True: while True:
# time.sleep(0.01) # time.sleep(0.01)
threading.Event().wait(0.01) threading.Event().wait(0.01)
@ -830,8 +875,14 @@ class RF:
if not static.TRANSMITTING: if not static.TRANSMITTING:
dfft[dfft > avg + 10] = 100 dfft[dfft > avg + 10] = 100
# Calculate audio max value # Calculate audio RMS
# static.AUDIO_RMS = np.amax(self.fft_data) # https://stackoverflow.com/a/9763652
# calculate RMS every 50 cycles for reducing CPU load
rms_counter += 1
if rms_counter > 50:
d = np.frombuffer(self.fft_data, np.int16).astype(np.float)
static.AUDIO_RMS = int(np.sqrt(np.mean(d ** 2)))
rms_counter = 0
# Check for signals higher than average by checking for "100" # Check for signals higher than average by checking for "100"
# If we have a signal, increment our channel_busy delay counter # If we have a signal, increment our channel_busy delay counter
@ -870,8 +921,8 @@ class RF:
frames_per_burst = min(frames_per_burst, 1) frames_per_burst = min(frames_per_burst, 1)
frames_per_burst = max(frames_per_burst, 5) frames_per_burst = max(frames_per_burst, 5)
codec2.api.freedv_set_frames_per_burst(self.datac1_freedv, frames_per_burst) codec2.api.freedv_set_frames_per_burst(self.dat0_datac1_freedv, frames_per_burst)
codec2.api.freedv_set_frames_per_burst(self.datac3_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.fsk_ldpc_freedv_0, frames_per_burst) codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, frames_per_burst)

View file

@ -85,7 +85,7 @@ ENABLE_FFT: bool = False
CHANNEL_BUSY: bool = False CHANNEL_BUSY: bool = False
# ARQ PROTOCOL VERSION # ARQ PROTOCOL VERSION
ARQ_PROTOCOL_VERSION: int = 2 ARQ_PROTOCOL_VERSION: int = 3
# ARQ statistics # ARQ statistics
ARQ_BYTES_PER_MINUTE_BURST: int = 0 ARQ_BYTES_PER_MINUTE_BURST: int = 0
@ -149,4 +149,5 @@ class FRAME_TYPE(Enum):
ARQ_DC_OPEN_ACK_N = 228 ARQ_DC_OPEN_ACK_N = 228
ARQ_STOP = 249 ARQ_STOP = 249
BEACON = 250 BEACON = 250
IDENT = 254
TEST_FRAME = 255 TEST_FRAME = 255