mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
Merge pull request #259 from DJ2LS/ls-arq
WIP: Move to session id instead of callsign crc check
This commit is contained in:
commit
aaea0d1509
10
.github/workflows/ctest.yml
vendored
10
.github/workflows/ctest.yml
vendored
|
@ -13,12 +13,18 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
|
||||
- name: Install packages
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install octave octave-common octave-signal sox python3 python3-pip portaudio19-dev python3-pyaudio
|
||||
pip3 install psutil crcengine ujson pyserial numpy structlog sounddevice
|
||||
sudo apt-get install octave octave-common octave-signal sox portaudio19-dev python3-pyaudio
|
||||
pip3 install psutil crcengine ujson pyserial numpy structlog sounddevice pyaudio
|
||||
pip3 install pytest pytest-rerunfailures
|
||||
|
||||
- name: Build codec2
|
||||
|
|
|
@ -1458,11 +1458,10 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
|||
document.getElementById("beaconInterval").disabled = false;
|
||||
}
|
||||
// RMS
|
||||
/*
|
||||
var rms_level = Math.round((arg.rms_level/60) * 100)
|
||||
var rms_level = (arg.rms_level / 32767) * 100
|
||||
document.getElementById("rms_level").setAttribute("aria-valuenow", rms_level);
|
||||
document.getElementById("rms_level").setAttribute("style", "width:" + rms_level + "%;");
|
||||
*/
|
||||
|
||||
|
||||
// SET FREQUENCY
|
||||
document.getElementById("frequency").innerHTML = arg.frequency;
|
||||
|
|
|
@ -746,11 +746,11 @@
|
|||
<div class="card-body p-2">
|
||||
<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>
|
||||
<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 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 bg-success" role="progressbar" style="width: 80%" aria-valuenow="80" 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: 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>
|
||||
</div>
|
||||
|
|
|
@ -15,6 +15,7 @@ Uses util_datac0.py in separate process to perform the data transfer.
|
|||
"""
|
||||
|
||||
import multiprocessing
|
||||
from random import randbytes
|
||||
import sys
|
||||
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_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[:1] = bytes([frame_type])
|
||||
frame[1:4] = dxcallsign_crc
|
||||
frame[4:7] = mycallsign_crc
|
||||
frame[7:13] = mycallsign_bytes
|
||||
frame[1:2] = session_id
|
||||
frame[2:5] = dxcallsign_crc
|
||||
frame[5:8] = mycallsign_crc
|
||||
frame[8:14] = mycallsign_bytes
|
||||
|
||||
return frame
|
||||
|
||||
|
||||
def t_create_session_close(mycall: str, dxcall: str) -> bytearray:
|
||||
def t_create_session_close_old(mycall: str, dxcall: str) -> bytearray:
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
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:
|
||||
"""
|
||||
Generate the create_session frame.
|
||||
|
@ -150,18 +175,24 @@ def t_foreign_disconnect(mycall: str, dxcall: str):
|
|||
assert static.ARQ_SESSION_STATE == "connecting"
|
||||
|
||||
# Set up a frame from a non-associated station.
|
||||
foreigncall_bytes = helpers.callsign_to_bytes("ZZ0ZZ-0")
|
||||
foreigncall = helpers.bytes_to_callsign(foreigncall_bytes)
|
||||
# foreigncall_bytes = helpers.callsign_to_bytes("ZZ0ZZ-0")
|
||||
# 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)
|
||||
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 (
|
||||
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."
|
||||
# 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 (
|
||||
# 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
|
||||
tnc.received_session_close(close_frame)
|
||||
|
@ -221,7 +252,9 @@ def t_valid_disconnect(mycall: str, dxcall: str):
|
|||
assert static.ARQ_SESSION_STATE == "connecting"
|
||||
|
||||
# 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)
|
||||
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("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):
|
||||
proc = multiprocessing.Process(target=t_foreign_disconnect, args=(mycall, dxcall))
|
||||
# print("Starting threads.")
|
||||
|
|
|
@ -42,8 +42,8 @@ def get_audio_devices():
|
|||
proc.start()
|
||||
proc.join()
|
||||
|
||||
#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: input_devices:", list=f"{proxy_input_devices}")
|
||||
log.debug("[AUD] get_audio_devices: output_devices:", list=f"{proxy_output_devices}")
|
||||
return list(proxy_input_devices), list(proxy_output_devices)
|
||||
|
||||
|
||||
|
|
|
@ -23,7 +23,8 @@ class FREEDV_MODE(Enum):
|
|||
"""
|
||||
Enumeration for codec2 modes and names
|
||||
"""
|
||||
|
||||
sig0 = 14
|
||||
sig1 = 14
|
||||
datac0 = 14
|
||||
datac1 = 10
|
||||
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.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.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
|
||||
|
||||
# ------- MODEM STATS STRUCTURES
|
||||
MODEM_STATS_NC_MAX = 50 + 1
|
||||
MODEM_STATS_NR_MAX = 160
|
||||
MODEM_STATS_NC_MAX = 50 + 1 * 2
|
||||
MODEM_STATS_NR_MAX = 160 * 2
|
||||
MODEM_STATS_ET_MAX = 8
|
||||
MODEM_STATS_EYE_IND_MAX = 160
|
||||
MODEM_STATS_NSPEC = 512
|
||||
|
@ -233,10 +237,12 @@ class MODEMSTATS(ctypes.Structure):
|
|||
("pre", ctypes.c_int),
|
||||
("post", 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
|
||||
("neyesamp", ctypes.c_int), # How many samples in the eye diagram
|
||||
("f_est", (ctypes.c_float * MODEM_STATS_MAX_F_EST)),
|
||||
("fft_buf", (ctypes.c_float * MODEM_STATS_NSPEC * 2)),
|
||||
("fft_cfg", ctypes.c_void_p)
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import threading
|
|||
import time
|
||||
import uuid
|
||||
import zlib
|
||||
from random import randrange
|
||||
from random import randrange, randbytes
|
||||
|
||||
import codec2
|
||||
import helpers
|
||||
|
@ -44,7 +44,11 @@ class DATA:
|
|||
self.data_queue_received = DATA_QUEUE_RECEIVED
|
||||
|
||||
# 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
|
||||
self.arq_file_transfer = False
|
||||
|
@ -53,6 +57,9 @@ class DATA:
|
|||
self.arq_session_timeout = 30
|
||||
self.session_connect_max_retries = 15
|
||||
|
||||
# actual n retries of burst
|
||||
self.tx_n_retry_of_burst = 0
|
||||
|
||||
self.transmission_uuid = ""
|
||||
|
||||
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
|
||||
self.data_frame_eof = b"EOF"
|
||||
|
||||
self.tx_n_max_retries_per_burst = 50
|
||||
self.rx_n_max_retries_per_burst = 50
|
||||
self.n_retries_per_burst = 0
|
||||
|
||||
|
@ -128,7 +136,11 @@ class DATA:
|
|||
self.rx_frame_bof_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
|
||||
# instead of a long series of if-elif-else statements.
|
||||
|
@ -278,11 +290,21 @@ class DATA:
|
|||
# bytes_out[2:5] == transmission
|
||||
# we could also create an own function, which returns True.
|
||||
frametype = int.from_bytes(bytes(bytes_out[:1]), "big")
|
||||
|
||||
# check for callsign CRC
|
||||
_valid1, _ = helpers.check_callsign(self.mycallsign, bytes(bytes_out[1:4]))
|
||||
_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 (
|
||||
_valid1
|
||||
or _valid2
|
||||
or _valid3
|
||||
or _valid4
|
||||
or frametype
|
||||
in [
|
||||
FR_TYPE.CQ.value,
|
||||
|
@ -293,8 +315,8 @@ class DATA:
|
|||
):
|
||||
|
||||
# CHECK IF FRAMETYPE IS BETWEEN 10 and 50 ------------------------
|
||||
frame = frametype - 10
|
||||
n_frames_per_burst = int.from_bytes(bytes(bytes_out[1:2]), "big")
|
||||
# frame = frametype - 10
|
||||
# n_frames_per_burst = int.from_bytes(bytes(bytes_out[1:2]), "big")
|
||||
|
||||
# Dispatch activity based on received frametype
|
||||
if frametype in self.rx_dispatcher:
|
||||
|
@ -339,7 +361,7 @@ class DATA:
|
|||
|
||||
def enqueue_frame_for_tx(
|
||||
self,
|
||||
frame_to_tx: bytearray,
|
||||
frame_to_tx: list[bytearray],
|
||||
c2_mode=FREEDV_MODE.datac0.value,
|
||||
copies=1,
|
||||
repeat_delay=0,
|
||||
|
@ -348,7 +370,7 @@ class DATA:
|
|||
Send (transmit) supplied frame to TNC
|
||||
|
||||
: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)
|
||||
:type c2_mode: int, optional
|
||||
: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
|
||||
# TODO: This is not that nice, we could improve this somehow
|
||||
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
|
||||
while static.TRANSMITTING:
|
||||
|
@ -390,32 +412,48 @@ class DATA:
|
|||
json_data_out = json.dumps(jsondata)
|
||||
sock.SOCKET_QUEUE.put(json_data_out)
|
||||
|
||||
def send_burst_ack_frame(self, snr) -> None:
|
||||
"""Build and send ACK frame for burst DATA frame"""
|
||||
ack_frame = bytearray(self.length_sig_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[7:8] = bytes([int(snr)])
|
||||
ack_frame[8:9] = bytes([int(self.speed_level)])
|
||||
def send_ident_frame(self, transmit) -> None:
|
||||
"""Build and send IDENT frame """
|
||||
ident_frame = bytearray(self.length_sig1_frame)
|
||||
ident_frame[:1] = bytes([FR_TYPE.IDENT.value])
|
||||
ident_frame[1:self.length_sig1_frame] = self.mycallsign
|
||||
|
||||
# Transmit frame
|
||||
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:
|
||||
"""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:4] = static.DXCALLSIGN_CRC
|
||||
ack_frame[4:7] = static.MYCALLSIGN_CRC
|
||||
ack_frame[7:8] = bytes([int(snr)])
|
||||
ack_frame[8:9] = bytes([int(self.speed_level)])
|
||||
ack_frame[1:2] = self.session_id
|
||||
# ack_frame[1:4] = static.DXCALLSIGN_CRC
|
||||
# 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
|
||||
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:
|
||||
# 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.
|
||||
missing_frames = [
|
||||
frame + 1
|
||||
|
@ -429,51 +467,60 @@ class DATA:
|
|||
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_sig_frame)
|
||||
rpt_frame = bytearray(self.length_sig1_frame)
|
||||
rpt_frame[:1] = bytes([FR_TYPE.FR_REPEAT.value])
|
||||
rpt_frame[1:4] = static.DXCALLSIGN_CRC
|
||||
rpt_frame[4:7] = static.MYCALLSIGN_CRC
|
||||
rpt_frame[7:13] = missing_frames
|
||||
rpt_frame[1:2] = self.session_id
|
||||
# rpt_frame[1:4] = static.DXCALLSIGN_CRC
|
||||
# rpt_frame[4:7] = static.MYCALLSIGN_CRC
|
||||
# rpt_frame[7:13] = missing_frames
|
||||
|
||||
self.log.info("[TNC] ARQ | RX | Requesting", frames=missing_frames)
|
||||
# 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:
|
||||
"""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:4] = static.DXCALLSIGN_CRC
|
||||
nack_frame[4:7] = static.MYCALLSIGN_CRC
|
||||
nack_frame[7:8] = bytes([int(snr)])
|
||||
nack_frame[8:9] = bytes([int(self.speed_level)])
|
||||
nack_frame[1:2] = self.session_id
|
||||
# nack_frame[1:4] = static.DXCALLSIGN_CRC
|
||||
# nack_frame[4:7] = static.MYCALLSIGN_CRC
|
||||
nack_frame[2:3] = bytes([int(snr)])
|
||||
nack_frame[3:4] = bytes([int(self.speed_level)])
|
||||
|
||||
# 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:
|
||||
|
||||
"""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:4] = static.DXCALLSIGN_CRC
|
||||
nack_frame[4:7] = static.MYCALLSIGN_CRC
|
||||
nack_frame[7:8] = bytes([int(snr)])
|
||||
nack_frame[8:9] = bytes([int(self.speed_level)])
|
||||
nack_frame[1:2] = self.session_id
|
||||
# nack_frame[1:4] = static.DXCALLSIGN_CRC
|
||||
# nack_frame[4:7] = static.MYCALLSIGN_CRC
|
||||
nack_frame[2:3] = bytes([int(snr)])
|
||||
nack_frame[3:4] = bytes([int(self.speed_level)])
|
||||
|
||||
# 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:
|
||||
"""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:4] = static.DXCALLSIGN_CRC
|
||||
disconnection_frame[4:7] = static.MYCALLSIGN_CRC
|
||||
disconnection_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign)
|
||||
|
||||
self.enqueue_frame_for_tx(disconnection_frame, copies=5, repeat_delay=250)
|
||||
disconnection_frame[1:2] = self.session_id
|
||||
# disconnection_frame[1:4] = static.DXCALLSIGN_CRC
|
||||
# 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, 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(
|
||||
self, data_in: bytes, bytes_per_frame: int, snr: float, freedv
|
||||
|
@ -491,11 +538,14 @@ class DATA:
|
|||
# is intended for this station.
|
||||
data_in = bytes(data_in)
|
||||
|
||||
# TODO: this seems not to work anymore
|
||||
# get received crc for different mycall ssids
|
||||
# check if callsign ssid override
|
||||
_, mycallsign = helpers.check_callsign(
|
||||
self.mycallsign, data_in[2:5]
|
||||
)
|
||||
# _, mycallsign = helpers.check_callsign(
|
||||
# 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
|
||||
if not static.ARQ_STATE and static.TNC_STATE != "BUSY":
|
||||
|
@ -511,19 +561,20 @@ class DATA:
|
|||
|
||||
# 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
|
||||
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")
|
||||
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(static.RX_BURST_BUFFER) != rx_n_frames_per_burst:
|
||||
static.RX_BURST_BUFFER = [None] * 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[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)
|
||||
|
||||
|
@ -540,7 +591,7 @@ class DATA:
|
|||
# This is the ideal case because we received all data
|
||||
if None not in static.RX_BURST_BUFFER:
|
||||
# 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""
|
||||
for value in static.RX_BURST_BUFFER:
|
||||
# static.RX_FRAME_BUFFER += static.RX_BURST_BUFFER[i]
|
||||
|
@ -603,9 +654,8 @@ class DATA:
|
|||
)
|
||||
static.ARQ_SPEED_LEVEL = self.speed_level
|
||||
|
||||
|
||||
# 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
|
||||
self.log.info("[TNC] ARQ | RX | SENDING ACK")
|
||||
|
@ -619,7 +669,7 @@ class DATA:
|
|||
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,
|
||||
# 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
|
||||
|
@ -627,8 +677,8 @@ class DATA:
|
|||
# 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,
|
||||
frame=rx_n_frame_of_burst,
|
||||
frames=rx_n_frames_per_burst,
|
||||
)
|
||||
self.send_retransmit_request_frame(freedv)
|
||||
self.calculate_transfer_rate_rx(
|
||||
|
@ -639,8 +689,8 @@ class DATA:
|
|||
else:
|
||||
self.log.error(
|
||||
"[TNC] data_handler: Should not reach this point...",
|
||||
frame=RX_N_FRAME_OF_BURST,
|
||||
frames=RX_N_FRAMES_PER_BURST,
|
||||
frame=rx_n_frame_of_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.
|
||||
|
@ -649,7 +699,7 @@ class DATA:
|
|||
bof_position = static.RX_FRAME_BUFFER.find(self.data_frame_bof)
|
||||
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:
|
||||
payload = static.RX_FRAME_BUFFER[
|
||||
|
@ -755,6 +805,7 @@ class DATA:
|
|||
snr=snr,
|
||||
crc=data_frame_crc.hex(),
|
||||
)
|
||||
|
||||
self.send_data_ack_frame(snr)
|
||||
# Update statistics AFTER the frame ACK is sent
|
||||
self.calculate_transfer_rate_rx(
|
||||
|
@ -807,15 +858,13 @@ class DATA:
|
|||
"""
|
||||
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
|
||||
# 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
|
||||
# static.TOTAL_BYTES = round(len(data_out) / 1024, 2)
|
||||
|
@ -867,9 +916,9 @@ class DATA:
|
|||
|
||||
# Iterate through data_out buffer
|
||||
while not self.data_frame_ack_received and static.ARQ_STATE:
|
||||
# we have 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):
|
||||
data_mode = mode
|
||||
# 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):
|
||||
# data_mode = mode
|
||||
# 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
|
||||
|
@ -906,16 +955,17 @@ class DATA:
|
|||
# Tempbuffer list for storing our data frames
|
||||
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!
|
||||
# TX_N_FRAMES_PER_BURST = 1 is working
|
||||
# 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([TX_N_FRAMES_PER_BURST])
|
||||
arqheader[2:5] = static.DXCALLSIGN_CRC
|
||||
arqheader[5:8] = static.MYCALLSIGN_CRC
|
||||
arqheader[1:2] = bytes([n_frames_per_burst])
|
||||
arqheader[2:3] = self.session_id
|
||||
# arqheader[2:5] = static.DXCALLSIGN_CRC
|
||||
# arqheader[5:8] = static.MYCALLSIGN_CRC
|
||||
|
||||
bufferposition_end = bufferposition + payload_per_frame - len(arqheader)
|
||||
|
||||
|
@ -939,21 +989,21 @@ class DATA:
|
|||
self.log.info(
|
||||
"[TNC] ARQ | TX | FRAMES",
|
||||
mode=FREEDV_MODE(data_mode).name,
|
||||
fpb=TX_N_FRAMES_PER_BURST,
|
||||
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([t_buf_item], c2_mode=data_mode)
|
||||
|
||||
# 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
|
||||
# not self.rpt_request_received and not self.data_frame_ack_received and
|
||||
# time.time() < burstacktimeout and static.ARQ_STATE):
|
||||
# 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 (
|
||||
self.burst_ack
|
||||
or self.burst_nack
|
||||
|
@ -1000,7 +1050,7 @@ class DATA:
|
|||
self.log.debug(
|
||||
"[TNC] ATTEMPT:",
|
||||
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,
|
||||
)
|
||||
# End of FOR loop
|
||||
|
@ -1022,7 +1072,7 @@ class DATA:
|
|||
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
|
||||
# wait for an acknowledgement.
|
||||
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:
|
||||
"""
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
@ -1118,8 +1168,8 @@ class DATA:
|
|||
|
||||
# Update data_channel timestamp
|
||||
self.data_channel_last_received = int(time.time())
|
||||
self.burst_ack_snr = int.from_bytes(bytes(data_in[7:8]), "big")
|
||||
self.speed_level = int.from_bytes(bytes(data_in[8:9]), "big")
|
||||
self.burst_ack_snr = int.from_bytes(bytes(data_in[2:3]), "big")
|
||||
self.speed_level = int.from_bytes(bytes(data_in[3:4]), "big")
|
||||
static.ARQ_SPEED_LEVEL = self.speed_level
|
||||
|
||||
self.log.debug(
|
||||
|
@ -1272,11 +1322,16 @@ class DATA:
|
|||
self.IS_ARQ_SESSION_MASTER = True
|
||||
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:4] = static.DXCALLSIGN_CRC
|
||||
connection_frame[4:7] = static.MYCALLSIGN_CRC
|
||||
connection_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign)
|
||||
connection_frame[1:2] = self.session_id
|
||||
connection_frame[2:5] = static.DXCALLSIGN_CRC
|
||||
connection_frame[5:8] = static.MYCALLSIGN_CRC
|
||||
connection_frame[8:14] = helpers.callsign_to_bytes(self.mycallsign)
|
||||
|
||||
while not static.ARQ_SESSION:
|
||||
time.sleep(0.01)
|
||||
|
@ -1291,7 +1346,7 @@ class DATA:
|
|||
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`
|
||||
# indicates we've received a positive response from the far station.
|
||||
|
@ -1325,8 +1380,9 @@ class DATA:
|
|||
# Update arq_session timestamp
|
||||
self.arq_session_last_received = int(time.time())
|
||||
|
||||
static.DXCALLSIGN_CRC = bytes(data_in[4:7])
|
||||
static.DXCALLSIGN = helpers.bytes_to_callsign(bytes(data_in[7:13]))
|
||||
self.session_id = bytes(data_in[1:2])
|
||||
static.DXCALLSIGN_CRC = bytes(data_in[5:8])
|
||||
static.DXCALLSIGN = helpers.bytes_to_callsign(bytes(data_in[8:14]))
|
||||
|
||||
helpers.add_to_heard_stations(
|
||||
static.DXCALLSIGN,
|
||||
|
@ -1377,9 +1433,11 @@ class DATA:
|
|||
|
||||
self.IS_ARQ_SESSION_MASTER = 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.arq_cleanup()
|
||||
|
||||
def received_session_close(self, data_in: bytes):
|
||||
"""
|
||||
|
@ -1394,7 +1452,8 @@ class DATA:
|
|||
# is intended for this station.
|
||||
# Close the session if the CRC matches the remote station in static.
|
||||
_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"
|
||||
helpers.add_to_heard_stations(
|
||||
static.DXCALLSIGN,
|
||||
|
@ -1429,12 +1488,13 @@ class DATA:
|
|||
# static.TNC_STATE = "BUSY"
|
||||
# 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:4] = static.DXCALLSIGN_CRC
|
||||
connection_frame[4:7] = static.MYCALLSIGN_CRC
|
||||
connection_frame[1:2] = self.session_id
|
||||
# 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:
|
||||
"""
|
||||
|
@ -1443,9 +1503,10 @@ class DATA:
|
|||
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]))
|
||||
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")
|
||||
helpers.add_to_heard_stations(
|
||||
static.DXCALLSIGN,
|
||||
|
@ -1463,8 +1524,19 @@ class DATA:
|
|||
# Update the timeout timestamps
|
||||
self.arq_session_last_received = int(time.time())
|
||||
self.data_channel_last_received = int(time.time())
|
||||
|
||||
if not self.IS_ARQ_SESSION_MASTER and not self.arq_file_transfer:
|
||||
# transmit session heartbeat only
|
||||
# -> if not session master
|
||||
# --> this will be triggered by heartbeat watchdog
|
||||
# -> if not during a file transfer
|
||||
# -> if ARQ_SESSION_STATE != disconnecting, disconnected, failed
|
||||
# --> to avoid heartbeat toggle loops while disconnecting
|
||||
if (
|
||||
not self.IS_ARQ_SESSION_MASTER
|
||||
and not self.arq_file_transfer
|
||||
and static.ARQ_SESSION_STATE != 'disconnecting'
|
||||
and static.ARQ_SESSION_STATE != 'disconnected'
|
||||
and static.ARQ_SESSION_STATE != 'failed'
|
||||
):
|
||||
self.transmit_session_heartbeat()
|
||||
|
||||
##########################################################################################################
|
||||
|
@ -1485,6 +1557,8 @@ class DATA:
|
|||
data_out:bytes:
|
||||
mode:int:
|
||||
n_frames_per_burst:int:
|
||||
transmission_uuid:str:
|
||||
mycallsign:bytes:
|
||||
|
||||
Returns:
|
||||
True if the data session was opened and the data was sent
|
||||
|
@ -1499,12 +1573,13 @@ class DATA:
|
|||
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:
|
||||
time.sleep(0.5)
|
||||
time.sleep(2)
|
||||
|
||||
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
|
||||
# for calculating transmission statistics
|
||||
# static.ARQ_COMPRESSION_FACTOR = len(data_out) / len(zlib.compress(data_out))
|
||||
|
@ -1530,6 +1605,7 @@ class DATA:
|
|||
Args:
|
||||
mode:int:
|
||||
n_frames_per_burst:int:
|
||||
mycallsign:bytes:
|
||||
|
||||
Returns:
|
||||
True if the data channel was opened successfully
|
||||
|
@ -1537,6 +1613,11 @@ class DATA:
|
|||
"""
|
||||
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
|
||||
self.data_channel_last_received = int(time.time())
|
||||
|
||||
|
@ -1548,12 +1629,13 @@ class DATA:
|
|||
frametype = bytes([FR_TYPE.ARQ_DC_OPEN_W.value])
|
||||
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:4] = static.DXCALLSIGN_CRC
|
||||
connection_frame[4:7] = static.MYCALLSIGN_CRC
|
||||
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:
|
||||
time.sleep(0.01)
|
||||
|
@ -1574,7 +1656,7 @@ class DATA:
|
|||
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
|
||||
while time.time() < timeout:
|
||||
|
@ -1607,19 +1689,20 @@ class DATA:
|
|||
+ "]"
|
||||
)
|
||||
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.
|
||||
self.close_session()
|
||||
|
||||
self.arq_cleanup()
|
||||
return False
|
||||
|
||||
# Shouldn't get here..
|
||||
# Shouldn't get here...
|
||||
return True
|
||||
|
||||
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:
|
||||
data_in:bytes:
|
||||
|
@ -1699,7 +1782,7 @@ class DATA:
|
|||
)
|
||||
|
||||
# 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(
|
||||
static.DXCALLSIGN,
|
||||
|
@ -1710,6 +1793,9 @@ class DATA:
|
|||
static.HAMLIB_FREQUENCY,
|
||||
)
|
||||
|
||||
self.session_id = data_in[13:14]
|
||||
print(self.session_id)
|
||||
|
||||
# check if callsign ssid override
|
||||
_, 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])
|
||||
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:4] = static.DXCALLSIGN_CRC
|
||||
connection_frame[4:7] = static.MYCALLSIGN_CRC
|
||||
connection_frame[1:2] = self.session_id
|
||||
# connection_frame[1:4] = static.DXCALLSIGN_CRC
|
||||
# connection_frame[4:7] = static.MYCALLSIGN_CRC
|
||||
connection_frame[8:9] = bytes([self.speed_level])
|
||||
|
||||
# For checking protocol version on the receiving side
|
||||
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(
|
||||
"[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:4] = static.DXCALLSIGN_CRC
|
||||
ping_frame[4:7] = static.MYCALLSIGN_CRC
|
||||
|
@ -1868,9 +1955,9 @@ class DATA:
|
|||
|
||||
self.log.info("[TNC] ENABLE FSK", state=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:
|
||||
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:
|
||||
"""
|
||||
|
@ -1919,7 +2006,7 @@ class DATA:
|
|||
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:4] = static.DXCALLSIGN_CRC
|
||||
ping_frame[4:7] = static.MYCALLSIGN_CRC
|
||||
|
@ -1927,9 +2014,9 @@ class DATA:
|
|||
|
||||
self.log.info("[TNC] ENABLE FSK", state=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:
|
||||
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:
|
||||
"""
|
||||
|
@ -1976,13 +2063,14 @@ class DATA:
|
|||
Force a stop of the running 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:4] = static.DXCALLSIGN_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)
|
||||
|
||||
self.enqueue_frame_for_tx(stop_frame, copies=2, repeat_delay=250)
|
||||
self.enqueue_frame_for_tx([stop_frame], c2_mode=FREEDV_MODE.sig1.value, copies=6, repeat_delay=0)
|
||||
|
||||
static.TNC_STATE = "IDLE"
|
||||
static.ARQ_STATE = False
|
||||
|
@ -2041,7 +2129,7 @@ class DATA:
|
|||
"[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:7] = helpers.callsign_to_bytes(self.mycallsign)
|
||||
beacon_frame[9:13] = static.MYGRID[:4]
|
||||
|
@ -2049,11 +2137,12 @@ class DATA:
|
|||
|
||||
if static.ENABLE_FSK:
|
||||
self.enqueue_frame_for_tx(
|
||||
beacon_frame,
|
||||
[beacon_frame],
|
||||
c2_mode=FREEDV_MODE.fsk_ldpc_0.value,
|
||||
)
|
||||
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
|
||||
while (
|
||||
|
@ -2109,7 +2198,7 @@ class DATA:
|
|||
"""
|
||||
Transmit a CQ
|
||||
Args:
|
||||
Nothing
|
||||
self
|
||||
|
||||
Returns:
|
||||
Nothing
|
||||
|
@ -2119,7 +2208,7 @@ class DATA:
|
|||
freedata="tnc-message",
|
||||
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:7] = helpers.callsign_to_bytes(self.mycallsign)
|
||||
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])
|
||||
|
||||
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:
|
||||
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:
|
||||
"""
|
||||
|
@ -2190,7 +2279,7 @@ class DATA:
|
|||
)
|
||||
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:7] = helpers.callsign_to_bytes(self.mycallsign)
|
||||
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)
|
||||
|
||||
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:
|
||||
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:
|
||||
"""
|
||||
|
@ -2235,7 +2324,7 @@ class DATA:
|
|||
static.HAMLIB_FREQUENCY,
|
||||
)
|
||||
|
||||
# ------------ CALUCLATE TRANSFER RATES
|
||||
# ------------ CALCULATE TRANSFER RATES
|
||||
def calculate_transfer_rate_rx(
|
||||
self, rx_start_of_transmission: float, receivedbytes: int
|
||||
) -> list:
|
||||
|
@ -2258,7 +2347,7 @@ class DATA:
|
|||
(
|
||||
receivedbytes
|
||||
* static.ARQ_COMPRESSION_FACTOR
|
||||
/ (static.TOTAL_BYTES)
|
||||
/ static.TOTAL_BYTES
|
||||
)
|
||||
* 100
|
||||
),
|
||||
|
@ -2270,7 +2359,7 @@ class DATA:
|
|||
if receivedbytes > 0:
|
||||
static.ARQ_BITS_PER_SECOND = int((receivedbytes * 8) / transmissiontime)
|
||||
static.ARQ_BYTES_PER_MINUTE = int(
|
||||
(receivedbytes) / (transmissiontime / 60)
|
||||
receivedbytes / (transmissiontime / 60)
|
||||
)
|
||||
|
||||
else:
|
||||
|
@ -2324,7 +2413,7 @@ class DATA:
|
|||
|
||||
if sentbytes > 0:
|
||||
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:
|
||||
static.ARQ_BITS_PER_SECOND = 0
|
||||
|
@ -2353,6 +2442,7 @@ class DATA:
|
|||
|
||||
self.log.debug("[TNC] arq_cleanup")
|
||||
|
||||
self.session_id = bytes(1)
|
||||
self.rx_frame_bof_received = False
|
||||
self.rx_frame_eof_received = False
|
||||
self.burst_ack = False
|
||||
|
@ -2363,6 +2453,8 @@ class DATA:
|
|||
self.burst_ack_snr = 255
|
||||
|
||||
# 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_FSK_LDPC_0 = False
|
||||
|
@ -2403,15 +2495,20 @@ class DATA:
|
|||
self.rpt_request_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
|
||||
|
||||
Args:
|
||||
enable_sig0:int: Enable/Disable signalling mode 0
|
||||
enable_sig1:int: Enable/Disable signalling mode 1
|
||||
mode:int: Codec2 mode to listen for
|
||||
|
||||
"""
|
||||
# set modes we want to listen to
|
||||
modem.RECEIVE_SIG0 = enable_sig0
|
||||
modem.RECEIVE_SIG1 = enable_sig1
|
||||
|
||||
if mode == FREEDV_MODE.datac1.value:
|
||||
modem.RECEIVE_DATAC1 = True
|
||||
self.log.debug("[TNC] Changing listening data mode", mode="datac1")
|
||||
|
@ -2476,7 +2573,7 @@ class DATA:
|
|||
static.ARQ_SPEED_LEVEL = self.speed_level
|
||||
|
||||
# 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`?
|
||||
self.send_burst_nack_frame_watchdog(0)
|
||||
|
@ -2504,7 +2601,10 @@ class DATA:
|
|||
self.data_channel_last_received + self.transmission_timeout
|
||||
> 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())
|
||||
# pass
|
||||
else:
|
||||
|
@ -2558,12 +2658,15 @@ class DATA:
|
|||
Heartbeat thread which auto pauses and resumes the heartbeat signal when in an arq session
|
||||
"""
|
||||
while True:
|
||||
time.sleep(0.01)
|
||||
# additional check for smoother stopping if heartbeat transmission
|
||||
while not self.arq_file_transfer:
|
||||
time.sleep(0.01)
|
||||
if (
|
||||
static.ARQ_SESSION
|
||||
and self.IS_ARQ_SESSION_MASTER
|
||||
and static.ARQ_SESSION_STATE == "connected"
|
||||
and not self.arq_file_transfer
|
||||
# and not self.arq_file_transfer
|
||||
):
|
||||
time.sleep(1)
|
||||
self.transmit_session_heartbeat()
|
||||
|
@ -2572,5 +2675,5 @@ class DATA:
|
|||
def send_test_frame(self) -> None:
|
||||
"""Send an empty test frame"""
|
||||
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
|
||||
)
|
||||
|
|
|
@ -316,6 +316,21 @@ def check_callsign(callsign: bytes, crc_to_check: bytes):
|
|||
|
||||
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):
|
||||
"""
|
||||
|
|
355
tnc/modem.py
355
tnc/modem.py
|
@ -32,6 +32,8 @@ TXCHANNEL = ""
|
|||
static.TRANSMITTING = False
|
||||
|
||||
# Receive only specific modes to reduce CPU load
|
||||
RECEIVE_SIG0 = True
|
||||
RECEIVE_SIG1 = False
|
||||
RECEIVE_DATAC1 = False
|
||||
RECEIVE_DATAC3 = False
|
||||
RECEIVE_FSK_LDPC_1 = False
|
||||
|
@ -81,110 +83,63 @@ class RF:
|
|||
self.fft_data = bytes()
|
||||
|
||||
# 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.
|
||||
# self.datac0_payload_per_frame = self.datac0_bytes_per_frame - 2
|
||||
# self.datac0_n_nom_modem_samples = self.c_lib.freedv_get_n_nom_modem_samples(
|
||||
# self.datac0_freedv
|
||||
# )
|
||||
# self.datac0_n_tx_modem_samples = self.c_lib.freedv_get_n_tx_modem_samples(
|
||||
# self.datac0_freedv
|
||||
# )
|
||||
# self.datac0_n_tx_preamble_modem_samples = (
|
||||
# self.c_lib.freedv_get_n_tx_preamble_modem_samples(self.datac0_freedv)
|
||||
# )
|
||||
# self.datac0_n_tx_postamble_modem_samples = (
|
||||
# self.c_lib.freedv_get_n_tx_postamble_modem_samples(self.datac0_freedv)
|
||||
# )
|
||||
# DATAC0
|
||||
# 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)
|
||||
|
||||
# Datac1 - higher-bandwidth data frames
|
||||
self.datac1_freedv = ctypes.cast(
|
||||
codec2.api.freedv_open(codec2.api.FREEDV_MODE_DATAC1), ctypes.c_void_p
|
||||
)
|
||||
self.c_lib.freedv_set_tuning_range(
|
||||
self.datac1_freedv,
|
||||
ctypes.c_float(static.TUNING_RANGE_FMIN),
|
||||
ctypes.c_float(static.TUNING_RANGE_FMAX),
|
||||
)
|
||||
self.datac1_bytes_per_frame = int(
|
||||
codec2.api.freedv_get_bits_per_modem_frame(self.datac1_freedv) / 8
|
||||
)
|
||||
self.datac1_bytes_out = 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)
|
||||
# DATAC0
|
||||
# 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)
|
||||
|
||||
# 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
|
||||
self.fsk_ldpc_freedv_0 = ctypes.cast(
|
||||
codec2.api.freedv_open_advanced(
|
||||
# DATAC1
|
||||
self.dat0_datac1_freedv, \
|
||||
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,
|
||||
ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV),
|
||||
),
|
||||
ctypes.c_void_p,
|
||||
codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV
|
||||
)
|
||||
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
|
||||
self.fsk_ldpc_freedv_1 = ctypes.cast(
|
||||
codec2.api.freedv_open_advanced(
|
||||
# FSK LDPC - 1
|
||||
self.fsk_ldpc_freedv_1, \
|
||||
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,
|
||||
ctypes.byref(codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV),
|
||||
),
|
||||
ctypes.c_void_p,
|
||||
codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV
|
||||
)
|
||||
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
|
||||
if not TESTMODE:
|
||||
|
@ -242,6 +197,7 @@ class RF:
|
|||
|
||||
# --------------------------------------------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":
|
||||
import rig
|
||||
elif static.HAMLIB_RADIOCONTROL == "rigctl":
|
||||
|
@ -272,20 +228,25 @@ class RF:
|
|||
)
|
||||
fft_thread.start()
|
||||
|
||||
audio_thread_datac0 = threading.Thread(
|
||||
target=self.audio_datac0, name="AUDIO_THREAD DATAC0", daemon=True
|
||||
audio_thread_sig0_datac0 = threading.Thread(
|
||||
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(
|
||||
target=self.audio_datac1, name="AUDIO_THREAD DATAC1", daemon=True
|
||||
audio_thread_sig1_datac0 = threading.Thread(
|
||||
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(
|
||||
target=self.audio_datac3, name="AUDIO_THREAD DATAC3", daemon=True
|
||||
audio_thread_dat0_datac1 = threading.Thread(
|
||||
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:
|
||||
audio_thread_fsk_ldpc0 = threading.Thread(
|
||||
|
@ -335,9 +296,10 @@ class RF:
|
|||
|
||||
length_x = len(x)
|
||||
for data_buffer, receive in [
|
||||
(self.datac0_buffer, True),
|
||||
(self.datac1_buffer, RECEIVE_DATAC1),
|
||||
(self.datac3_buffer, RECEIVE_DATAC3),
|
||||
(self.sig0_datac0_buffer, RECEIVE_SIG0),
|
||||
(self.sig1_datac0_buffer, RECEIVE_SIG1),
|
||||
(self.dat0_datac1_buffer, RECEIVE_DATAC1),
|
||||
(self.dat0_datac3_buffer, RECEIVE_DATAC3),
|
||||
# Not enabled yet.
|
||||
# (self.fsk_ldpc_buffer_0, 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
|
||||
# selected datachannel mode is not full
|
||||
for audiobuffer, receive, index in [
|
||||
(self.datac0_buffer, True, 0),
|
||||
(self.datac1_buffer, RECEIVE_DATAC1, 1),
|
||||
(self.datac3_buffer, RECEIVE_DATAC3, 2),
|
||||
(self.fsk_ldpc_buffer_0, static.ENABLE_FSK, 3),
|
||||
(self.fsk_ldpc_buffer_1, static.ENABLE_FSK, 4),
|
||||
(self.sig0_datac0_buffer, RECEIVE_SIG0, 0),
|
||||
(self.sig1_datac0_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),
|
||||
]:
|
||||
if audiobuffer.nbuffer + length_x > audiobuffer.size:
|
||||
static.BUFFER_OVERFLOW_COUNTER[index] += 1
|
||||
|
@ -618,38 +581,119 @@ class RF:
|
|||
"[MDM] [demod_audio] Pushing received data to received_queue"
|
||||
)
|
||||
self.modem_received_queue.put([bytes_out, freedv, bytes_per_frame])
|
||||
# self.get_scatter(freedv)
|
||||
self.get_scatter(freedv)
|
||||
self.calculate_snr(freedv)
|
||||
return nin
|
||||
|
||||
def audio_datac0(self) -> None:
|
||||
"""Receive data encoded with datac0"""
|
||||
self.datac0_nin = self.demodulate_audio(
|
||||
self.datac0_buffer,
|
||||
self.datac0_nin,
|
||||
self.datac0_freedv,
|
||||
self.datac0_bytes_out,
|
||||
self.datac0_bytes_per_frame,
|
||||
def init_codec2_mode(self, mode, adv):
|
||||
"""
|
||||
Init codec2 and return some important parameters
|
||||
|
||||
Args:
|
||||
self:
|
||||
mode:
|
||||
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
|
||||
)
|
||||
|
||||
def audio_datac1(self) -> None:
|
||||
# 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),
|
||||
)
|
||||
|
||||
# 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"""
|
||||
self.datac1_nin = self.demodulate_audio(
|
||||
self.datac1_buffer,
|
||||
self.datac1_nin,
|
||||
self.datac1_freedv,
|
||||
self.datac1_bytes_out,
|
||||
self.datac1_bytes_per_frame,
|
||||
self.dat0_datac1_nin = self.demodulate_audio(
|
||||
self.dat0_datac1_buffer,
|
||||
self.dat0_datac1_nin,
|
||||
self.dat0_datac1_freedv,
|
||||
self.dat0_datac1_bytes_out,
|
||||
self.dat0_datac1_bytes_per_frame,
|
||||
)
|
||||
|
||||
def audio_datac3(self) -> None:
|
||||
def audio_dat0_datac3(self) -> None:
|
||||
"""Receive data encoded with datac3"""
|
||||
self.datac3_nin = self.demodulate_audio(
|
||||
self.datac3_buffer,
|
||||
self.datac3_nin,
|
||||
self.datac3_freedv,
|
||||
self.datac3_bytes_out,
|
||||
self.datac3_bytes_per_frame,
|
||||
self.dat0_datac3_nin = self.demodulate_audio(
|
||||
self.dat0_datac3_buffer,
|
||||
self.dat0_datac3_nin,
|
||||
self.dat0_datac3_freedv,
|
||||
self.dat0_datac3_bytes_out,
|
||||
self.dat0_datac3_bytes_per_frame,
|
||||
)
|
||||
|
||||
def audio_fsk_ldpc_0(self) -> None:
|
||||
|
@ -705,7 +749,6 @@ class RF:
|
|||
:rtype: float
|
||||
"""
|
||||
modemStats = codec2.MODEMSTATS()
|
||||
self.c_lib.freedv_get_modem_extended_stats.restype = None
|
||||
self.c_lib.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats))
|
||||
offset = round(modemStats.foff) * (-1)
|
||||
static.FREQ_OFFSET = offset
|
||||
|
@ -723,28 +766,27 @@ class RF:
|
|||
return
|
||||
|
||||
modemStats = codec2.MODEMSTATS()
|
||||
self.c_lib.freedv_get_modem_extended_stats.restype = None
|
||||
self.c_lib.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats))
|
||||
ctypes.cast(
|
||||
self.c_lib.freedv_get_modem_extended_stats(freedv, ctypes.byref(modemStats)),
|
||||
ctypes.c_void_p,
|
||||
)
|
||||
|
||||
scatterdata = []
|
||||
scatterdata_small = []
|
||||
for i in range(codec2.MODEM_STATS_NC_MAX):
|
||||
for j in range(codec2.MODEM_STATS_NR_MAX):
|
||||
# check if odd or not to get every 2nd item for x
|
||||
if (j % 2) == 0:
|
||||
xsymbols = round(modemStats.rx_symbols[i][j] / 1000)
|
||||
ysymbols = round(modemStats.rx_symbols[i][j + 1] / 1000)
|
||||
# check if value 0.0 or has real data
|
||||
for j in range(1, codec2.MODEM_STATS_NR_MAX, 2):
|
||||
# print(f"{modemStats.rx_symbols[i][j]} - {modemStats.rx_symbols[i][j]}")
|
||||
xsymbols = round(modemStats.rx_symbols[i][j - 1] // 1000)
|
||||
ysymbols = round(modemStats.rx_symbols[i][j] // 1000)
|
||||
if xsymbols != 0.0 and ysymbols != 0.0:
|
||||
scatterdata.append({"x": xsymbols, "y": ysymbols})
|
||||
scatterdata.append({"x": str(xsymbols), "y": str(ysymbols)})
|
||||
|
||||
# Send all the data if we have too-few samples, otherwise send a sampling
|
||||
if 150 > len(scatterdata) > 0:
|
||||
static.SCATTER = scatterdata
|
||||
else:
|
||||
# only take every tenth data point
|
||||
scatterdata_small = scatterdata[::10]
|
||||
static.SCATTER = scatterdata_small
|
||||
static.SCATTER = scatterdata[::10]
|
||||
|
||||
|
||||
def calculate_snr(self, freedv: ctypes.c_void_p) -> float:
|
||||
"""
|
||||
|
@ -801,6 +843,9 @@ class RF:
|
|||
# Initialize channel_busy_delay counter
|
||||
channel_busy_delay = 0
|
||||
|
||||
# Initialize rms counter
|
||||
rms_counter = 0
|
||||
|
||||
while True:
|
||||
# time.sleep(0.01)
|
||||
threading.Event().wait(0.01)
|
||||
|
@ -830,8 +875,14 @@ class RF:
|
|||
if not static.TRANSMITTING:
|
||||
dfft[dfft > avg + 10] = 100
|
||||
|
||||
# Calculate audio max value
|
||||
# static.AUDIO_RMS = np.amax(self.fft_data)
|
||||
# Calculate audio RMS
|
||||
# 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"
|
||||
# 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 = 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.datac3_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.dat0_datac3_freedv, frames_per_burst)
|
||||
codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, frames_per_burst)
|
||||
|
||||
|
||||
|
|
|
@ -85,7 +85,7 @@ ENABLE_FFT: bool = False
|
|||
CHANNEL_BUSY: bool = False
|
||||
|
||||
# ARQ PROTOCOL VERSION
|
||||
ARQ_PROTOCOL_VERSION: int = 2
|
||||
ARQ_PROTOCOL_VERSION: int = 3
|
||||
|
||||
# ARQ statistics
|
||||
ARQ_BYTES_PER_MINUTE_BURST: int = 0
|
||||
|
@ -149,4 +149,5 @@ class FRAME_TYPE(Enum):
|
|||
ARQ_DC_OPEN_ACK_N = 228
|
||||
ARQ_STOP = 249
|
||||
BEACON = 250
|
||||
IDENT = 254
|
||||
TEST_FRAME = 255
|
||||
|
|
Loading…
Reference in a new issue