Merge branch 'main' into qm-guiabout

This commit is contained in:
DJ2LS 2023-02-11 14:44:33 +01:00 committed by GitHub
commit 16ddbfc60a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1321 additions and 1289 deletions

View file

@ -21,7 +21,7 @@ jobs:
- python-version: "3.9"
- python-version: "3.10"
- python-version: "3.11"
- python-version: "3.12-dev"
#- python-version: "3.12-dev"
steps:
- uses: actions/checkout@v3

View file

@ -7,10 +7,7 @@ for c in colormaps:
cmap_name = c
cmap = plt.get_cmap(cmap_name)
colors = []
for i in range(256):
colors.append([int(round(255 * x)) for x in cmap(i)[:3]])
colors = [[int(round(255 * x)) for x in cmap(i)[:3]] for i in range(256)]
print(f'var {c} = {colors}')
print(f'var colormaps = [{", ".join(colormaps)}];')

View file

@ -85,11 +85,11 @@ def receive():
bytes_out = ctypes.c_ubyte * bytes_per_frame # bytes_per_frame
bytes_out = bytes_out() # get pointer from bytes_out
total_n_bytes = 0
rx_total_frames = 0
rx_frames = 0
rx_bursts = 0
receive = True
total_n_bytes = 0
while receive:
time.sleep(0.01)
@ -97,7 +97,7 @@ def receive():
nin_converted = int(nin * (AUDIO_SAMPLE_RATE_RX / MODEM_SAMPLE_RATE))
if DEBUGGING_MODE:
print("-----------------------------")
print("NIN: " + str(nin) + " [ " + str(nin_converted) + " ]")
print(f"NIN: {str(nin)} [ {nin_converted} ]")
data_in = stream_rx.read(nin_converted, exception_on_overflow=False)
data_in = data_in.rstrip(b"\x00")
@ -110,7 +110,7 @@ def receive():
nbytes = c_lib.freedv_rawdatarx(freedv, bytes_out, data_in) # demodulate audio
total_n_bytes = total_n_bytes + nbytes
if DEBUGGING_MODE:
print("SYNC: " + str(c_lib.freedv_get_rx_status(freedv)))
print(f"SYNC: {str(c_lib.freedv_get_rx_status(freedv))}")
if nbytes == bytes_per_frame:
rx_total_frames = rx_total_frames + 1
@ -127,15 +127,7 @@ def receive():
n_total_frame = bytes_out[3]
print(
"RX | PONG | BURST ["
+ str(burst)
+ "/"
+ str(n_total_burst)
+ "] FRAME ["
+ str(frame)
+ "/"
+ str(n_total_frame)
+ "]"
f"RX | PONG | BURST [{str(burst)}/{str(n_total_burst)}] FRAME [{str(frame)}/{str(n_total_frame)}]"
)
print("-----------------------------------------------------------------")
c_lib.freedv_set_sync(freedv, 0)
@ -151,7 +143,6 @@ RECEIVE.start()
c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte)
freedv = c_lib.freedv_open(FREEDV_TX_MODE)
bytes_per_frame = int(c_lib.freedv_get_bits_per_modem_frame(freedv) / 8)
payload_per_frame = bytes_per_frame - 2
n_nom_modem_samples = c_lib.freedv_get_n_nom_modem_samples(freedv)
n_tx_modem_samples = c_lib.freedv_get_n_tx_modem_samples(
freedv
@ -165,9 +156,10 @@ mod_out_preamble = ctypes.c_short * (
mod_out_preamble = mod_out_preamble()
print("BURSTS: " + str(N_BURSTS) + " FRAMES: " + str(N_FRAMES_PER_BURST))
print(f"BURSTS: {str(N_BURSTS)} FRAMES: {str(N_FRAMES_PER_BURST)}")
print("-----------------------------------------------------------------")
payload_per_frame = bytes_per_frame - 2
for i in range(N_BURSTS):
c_lib.freedv_rawdatapreambletx(freedv, mod_out_preamble)
@ -204,15 +196,7 @@ for i in range(N_BURSTS):
txbuffer += bytes(mod_out)
print(
"TX | PING | BURST ["
+ str(i + 1)
+ "/"
+ str(N_BURSTS)
+ "] FRAME ["
+ str(n + 1)
+ "/"
+ str(N_FRAMES_PER_BURST)
+ "]"
f"TX | PING | BURST [{str(i + 1)}/{str(N_BURSTS)}] FRAME [{str(n + 1)}/{str(N_FRAMES_PER_BURST)}]"
)
stream_tx.write(bytes(txbuffer))
ACK_TIMEOUT = time.time() + 3

View file

@ -81,7 +81,7 @@ c_lib.freedv_open.restype = ctypes.POINTER(ctypes.c_ubyte)
def send_pong(burst,n_total_burst,frame,n_total_frame):
data_out = bytearray()
data_out[0:1] = bytes([burst])
data_out[:1] = bytes([burst])
data_out[1:2] = bytes([n_total_burst])
data_out[2:3] = bytes([frame])
data_out[4:5] = bytes([n_total_frame])
@ -139,7 +139,7 @@ while receive:
nin_converted = int(nin*(AUDIO_SAMPLE_RATE_RX/MODEM_SAMPLE_RATE))
if DEBUGGING_MODE:
print("-----------------------------")
print("NIN: " + str(nin) + " [ " + str(nin_converted) + " ]")
print(f"NIN: {str(nin)} [ {nin_converted} ]")
data_in = stream_rx.read(nin_converted, exception_on_overflow = False)
data_in = data_in.rstrip(b'\x00')
@ -148,17 +148,19 @@ while receive:
nbytes = c_lib.freedv_rawdatarx(freedv, bytes_out, data_in) # demodulate audio
if DEBUGGING_MODE:
print("SYNC: " + str(c_lib.freedv_get_rx_status(freedv)))
print(f"SYNC: {str(c_lib.freedv_get_rx_status(freedv))}")
if nbytes == bytes_per_frame:
burst = bytes_out[0]
n_total_burst = bytes_out[1]
frame = bytes_out[2]
n_total_frame = bytes_out[3]
print("RX | BURST [" + str(burst) + "/" + str(n_total_burst) + "] FRAME [" + str(frame) + "/" + str(n_total_frame) + "] >>> SENDING PONG")
burst = bytes_out[0]
n_total_burst = bytes_out[1]
frame = bytes_out[2]
n_total_frame = bytes_out[3]
print(
f"RX | BURST [{str(burst)}/{str(n_total_burst)}] FRAME [{str(frame)}/{str(n_total_frame)}] >>> SENDING PONG"
)
TRANSMIT_PONG = threading.Thread(target=send_pong, args=[burst,n_total_burst,frame,n_total_frame], name="SEND PONG")
TRANSMIT_PONG.start()
TRANSMIT_PONG = threading.Thread(target=send_pong, args=[burst,n_total_burst,frame,n_total_frame], name="SEND PONG")
TRANSMIT_PONG.start()
c_lib.freedv_set_sync(freedv,0)
c_lib.freedv_set_sync(freedv,0)

View file

@ -77,10 +77,12 @@ class Test:
self.p = pyaudio.PyAudio()
# auto search for loopback devices
if self.AUDIO_INPUT_DEVICE == -2:
loopback_list = []
for dev in range(self.p.get_device_count()):
if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"]:
loopback_list.append(dev)
loopback_list = [
dev
for dev in range(self.p.get_device_count())
if "Loopback: PCM"
in self.p.get_device_info_by_index(dev)["name"]
]
if len(loopback_list) >= 2:
# 0 = RX 1 = TX
self.AUDIO_INPUT_DEVICE = loopback_list[0]
@ -287,7 +289,7 @@ class Test:
def run_audio(self):
try:
print(f"starting pyaudio callback", file=sys.stderr)
print("starting pyaudio callback", file=sys.stderr)
self.stream_rx.start_stream()
except Exception as e:
print(f"pyAudio error: {e}", file=sys.stderr)

View file

@ -219,7 +219,7 @@ class Test:
def run_audio(self):
try:
print(f"starting pyaudio callback", file=sys.stderr)
print("starting pyaudio callback", file=sys.stderr)
self.stream_rx.start_stream()
except Exception as e:
print(f"pyAudio error: {e}", file=sys.stderr)

View file

@ -138,7 +138,7 @@ class Test:
def run_audio(self):
try:
print(f"starting pyaudio callback", file=sys.stderr)
print("starting pyaudio callback", file=sys.stderr)
self.stream_tx.start_stream()
except Exception as e:
print(f"pyAudio error: {e}", file=sys.stderr)

View file

@ -81,10 +81,12 @@ class Test:
self.p = pyaudio.PyAudio()
# auto search for loopback devices
if self.AUDIO_INPUT_DEVICE == -2:
loopback_list = []
for dev in range(self.p.get_device_count()):
if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"]:
loopback_list.append(dev)
loopback_list = [
dev
for dev in range(self.p.get_device_count())
if "Loopback: PCM"
in self.p.get_device_info_by_index(dev)["name"]
]
if len(loopback_list) >= 2:
self.AUDIO_INPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
@ -185,7 +187,7 @@ class Test:
def run_audio(self):
try:
print(f"starting pyaudio callback", file=sys.stderr)
print("starting pyaudio callback", file=sys.stderr)
self.stream_rx.start_stream()
except Exception as e:
print(f"pyAudio error: {e}", file=sys.stderr)

View file

@ -81,10 +81,12 @@ class Test:
self.p = pyaudio.PyAudio()
# auto search for loopback devices
if self.AUDIO_INPUT_DEVICE == -2:
loopback_list = []
for dev in range(self.p.get_device_count()):
if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"]:
loopback_list.append(dev)
loopback_list = [
dev
for dev in range(self.p.get_device_count())
if "Loopback: PCM"
in self.p.get_device_info_by_index(dev)["name"]
]
if len(loopback_list) >= 2:
self.AUDIO_INPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
@ -147,7 +149,7 @@ class Test:
def run_audio(self):
try:
print(f"starting pyaudio callback", file=sys.stderr)
print("starting pyaudio callback", file=sys.stderr)
self.stream_rx.start_stream()
except Exception as e:
print(f"pyAudio error: {e}", file=sys.stderr)

View file

@ -87,10 +87,12 @@ class Test:
self.p = pyaudio.PyAudio()
# auto search for loopback devices
if self.AUDIO_OUTPUT_DEVICE == -2:
loopback_list = []
for dev in range( self.p.get_device_count()):
if "Loopback: PCM" in self.p.get_device_info_by_index(dev)["name"]:
loopback_list.append(dev)
loopback_list = [
dev
for dev in range(self.p.get_device_count())
if "Loopback: PCM"
in self.p.get_device_info_by_index(dev)["name"]
]
if len(loopback_list) >= 2:
self.AUDIO_OUTPUT_DEVICE = loopback_list[0] # 0 = RX 1 = TX
print(f"loopback_list rx: {loopback_list}", file=sys.stderr)
@ -148,7 +150,7 @@ class Test:
def run_audio(self):
try:
print(f"starting pyaudio callback", file=sys.stderr)
print("starting pyaudio callback", file=sys.stderr)
self.stream_tx.start_stream()
except Exception as e:
print(f"pyAudio error: {e}", file=sys.stderr)

View file

@ -39,6 +39,10 @@ def t_setup(
):
# Disable data_handler testmode - This is required to test a conversation.
data_handler.TESTMODE = False
# Enable socket testmode for overriding socket class
sock.TESTMODE = True
modem.RXCHANNEL = tmp_path / rx_channel
modem.TESTMODE = True
modem.TXCHANNEL = tmp_path / tx_channel
@ -147,9 +151,9 @@ def t_datac0_1(
log.debug("t_datac0_1: STOP test, setting TNC state")
static.TNC_STATE = "BUSY"
static.ARQ_STATE = True
sock.process_tnc_commands(json.dumps(data, indent=None))
sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None))
time.sleep(0.5)
sock.process_tnc_commands(json.dumps(data, indent=None))
sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None))
# Assure the test completes.
timeout = time.time() + timeout_duration
@ -167,7 +171,7 @@ def t_datac0_1(
# override ARQ SESSION STATE for allowing disconnect command
static.ARQ_SESSION_STATE = "connected"
data = {"type": "arq", "command": "disconnect", "dxcallsign": dxcall}
sock.process_tnc_commands(json.dumps(data, indent=None))
sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None))
time.sleep(0.5)
# Allow enough time for this side to process the disconnect frame.
@ -260,8 +264,8 @@ def t_datac0_2(
if "cq" in data:
t_data = {"type": "arq", "command": "stop_transmission"}
sock.process_tnc_commands(json.dumps(t_data, indent=None))
sock.process_tnc_commands(json.dumps(t_data, indent=None))
sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(t_data, indent=None))
sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(t_data, indent=None))
# Assure the test completes.
timeout = time.time() + timeout_duration

View file

@ -33,6 +33,10 @@ def t_setup(
):
# Disable data_handler testmode - This is required to test a conversation.
data_handler.TESTMODE = False
# Enable socket testmode for overriding socket class
sock.TESTMODE = True
modem.RXCHANNEL = tmp_path / rx_channel
modem.TESTMODE = True
modem.TXCHANNEL = tmp_path / tx_channel
@ -148,8 +152,8 @@ def t_datac0_1(
static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN)
static.TNC_STATE = "BUSY"
static.ARQ_STATE = True
sock.process_tnc_commands(json.dumps(data, indent=None))
sock.process_tnc_commands(json.dumps(data, indent=None))
sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None))
sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None))
# Assure the test completes.
timeout = time.time() + timeout_duration
@ -173,7 +177,7 @@ def t_datac0_1(
# override ARQ SESSION STATE for allowing disconnect command
static.ARQ_SESSION_STATE = "connected"
data = {"type": "arq", "command": "disconnect", "dxcallsign": dxcall}
sock.process_tnc_commands(json.dumps(data, indent=None))
sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None))
time.sleep(0.5)
# Allow enough time for this side to process the disconnect frame.
@ -266,8 +270,8 @@ def t_datac0_2(
if "cq" in data:
t_data = {"type": "arq", "command": "stop_transmission"}
sock.process_tnc_commands(json.dumps(t_data, indent=None))
sock.process_tnc_commands(json.dumps(t_data, indent=None))
sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(t_data, indent=None))
sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(t_data, indent=None))
# Assure the test completes.
timeout = time.time() + timeout_duration

View file

@ -26,8 +26,6 @@ from tnc import codec2
def test_mm_tx():
# AUDIO PARAMETERS
AUDIO_FRAMES_PER_BUFFER = 2400
MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
AUDIO_SAMPLE_RATE_TX = 48000
assert (AUDIO_SAMPLE_RATE_TX % MODEM_SAMPLE_RATE) == 0
@ -72,6 +70,8 @@ def test_mm_tx():
else:
sys.exit()
# AUDIO PARAMETERS
AUDIO_FRAMES_PER_BUFFER = 2400
# pyaudio init
stream_tx = p_audio.open(
format=pyaudio.paInt16,

View file

@ -121,99 +121,93 @@ def util_rx():
time_start = 0
time_end = 0
# Copy received 48 kHz to a file. Listen to this file with:
# aplay -r 48000 -f S16_LE rx48.raw
# Corruption of this file is a good way to detect audio card issues
frx = open("rx48.raw", mode="wb")
with open("rx48.raw", mode="wb") as frx:
# initial number of samples we need
nin = codec2.api.freedv_nin(freedv)
while receive and time.time() < timeout:
if AUDIO_INPUT_DEVICE != -1:
try:
# data_in48k = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = True)
data_in48k, overflowed = stream_rx.read(AUDIO_FRAMES_PER_BUFFER) # type: ignore
except OSError as err:
print(err, file=sys.stderr)
# if str(err).find("Input overflowed") != -1:
# nread_exceptions += 1
# if str(err).find("Stream closed") != -1:
# print("Ending...")
# receive = False
else:
data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER * 2)
# initial number of samples we need
nin = codec2.api.freedv_nin(freedv)
while receive and time.time() < timeout:
if AUDIO_INPUT_DEVICE != -1:
try:
# data_in48k = stream_rx.read(AUDIO_FRAMES_PER_BUFFER, exception_on_overflow = True)
data_in48k, overflowed = stream_rx.read(AUDIO_FRAMES_PER_BUFFER) # type: ignore
except OSError as err:
print(err, file=sys.stderr)
# if str(err).find("Input overflowed") != -1:
# nread_exceptions += 1
# if str(err).find("Stream closed") != -1:
# print("Ending...")
# receive = False
else:
data_in48k = sys.stdin.buffer.read(AUDIO_FRAMES_PER_BUFFER * 2)
# insert samples in buffer
x = np.frombuffer(data_in48k, dtype=np.int16) # type: ignore
# print(x)
# x = data_in48k
x.tofile(frx)
if len(x) != AUDIO_FRAMES_PER_BUFFER:
receive = False
x = resampler.resample48_to_8(x)
audio_buffer.push(x)
# insert samples in buffer
x = np.frombuffer(data_in48k, dtype=np.int16) # type: ignore
# print(x)
# x = data_in48k
x.tofile(frx)
if len(x) != AUDIO_FRAMES_PER_BUFFER:
receive = False
x = resampler.resample48_to_8(x)
audio_buffer.push(x)
# when we have enough samples call FreeDV Rx
while audio_buffer.nbuffer >= nin:
# start time measurement
time_start = time.time()
# demodulate audio
nbytes = codec2.api.freedv_rawdatarx(
freedv, bytes_out, audio_buffer.buffer.ctypes
)
time_end = time.time()
audio_buffer.pop(nin)
# call me on every loop!
nin = codec2.api.freedv_nin(freedv)
rx_status = codec2.api.freedv_get_rx_status(freedv)
if rx_status & codec2.api.FREEDV_RX_BIT_ERRORS:
rx_errors = rx_errors + 1
if DEBUGGING_MODE:
rx_status = codec2.api.rx_sync_flags_to_text[rx_status] # type: ignore
time_needed = time_end - time_start
print(
f"nin: {nin:5d} rx_status: {rx_status:4s} "
f"naudio_buffer: {audio_buffer.nbuffer:4d} time: {time_needed:4f}",
file=sys.stderr,
# when we have enough samples call FreeDV Rx
while audio_buffer.nbuffer >= nin:
# start time measurement
time_start = time.time()
# demodulate audio
nbytes = codec2.api.freedv_rawdatarx(
freedv, bytes_out, audio_buffer.buffer.ctypes
)
time_end = time.time()
if nbytes:
total_n_bytes += nbytes
audio_buffer.pop(nin)
if nbytes == bytes_per_frame:
rx_total_frames += 1
rx_frames += 1
# call me on every loop!
nin = codec2.api.freedv_nin(freedv)
if rx_frames == N_FRAMES_PER_BURST:
rx_frames = 0
rx_bursts += 1
rx_status = codec2.api.freedv_get_rx_status(freedv)
if rx_status & codec2.api.FREEDV_RX_BIT_ERRORS:
rx_errors = rx_errors + 1
if DEBUGGING_MODE:
rx_status = codec2.api.rx_sync_flags_to_text[rx_status] # type: ignore
time_needed = time_end - time_start
if rx_bursts == N_BURSTS:
receive = False
print(
f"nin: {nin:5d} rx_status: {rx_status:4s} "
f"naudio_buffer: {audio_buffer.nbuffer:4d} time: {time_needed:4f}",
file=sys.stderr,
)
if time.time() >= timeout:
print("TIMEOUT REACHED")
if nbytes:
total_n_bytes += nbytes
time.sleep(0.01)
if nbytes == bytes_per_frame:
rx_total_frames += 1
rx_frames += 1
if nread_exceptions:
if rx_frames == N_FRAMES_PER_BURST:
rx_frames = 0
rx_bursts += 1
if rx_bursts == N_BURSTS:
receive = False
if time.time() >= timeout:
print("TIMEOUT REACHED")
time.sleep(0.01)
if nread_exceptions:
print(
f"nread_exceptions {nread_exceptions:d} - receive audio lost! "
"Consider increasing Pyaudio frames_per_buffer...",
file=sys.stderr,
)
print(
f"nread_exceptions {nread_exceptions:d} - receive audio lost! "
"Consider increasing Pyaudio frames_per_buffer...",
f"RECEIVED BURSTS: {rx_bursts} "
f"RECEIVED FRAMES: {rx_total_frames} "
f"RX_ERRORS: {rx_errors}",
file=sys.stderr,
)
print(
f"RECEIVED BURSTS: {rx_bursts} "
f"RECEIVED FRAMES: {rx_total_frames} "
f"RX_ERRORS: {rx_errors}",
file=sys.stderr,
)
frx.close()
# and at last check if we had an opened audio instance and close it
if AUDIO_INPUT_DEVICE != -1:
sd._terminate()

View file

@ -124,17 +124,17 @@ def t_arq_iss(*args):
time.sleep(0.5)
sock.process_tnc_commands(json.dumps(data, indent=None))
sock.process_tnc_commands(json.dumps(data, indent=None))
sock.process_tnc_commands(json.dumps(data, indent=None))
sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None))
sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None))
sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None))
time.sleep(1.5)
data = {"type": "arq", "command": "stop_transmission"}
sock.process_tnc_commands(json.dumps(data, indent=None))
sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None))
time.sleep(0.5)
sock.process_tnc_commands(json.dumps(data, indent=None))
sock.ThreadedTCPRequestHandler.process_tnc_commands(None,json.dumps(data, indent=None))
# Set timeout
timeout = time.time() + 15

View file

@ -135,221 +135,179 @@ class DAEMON:
# data[2] mygrid
# data[3] rx_audio
# data[4] tx_audio
# REMOVED - data[5] devicename
# REMOVED - data[6] deviceport
# REMOVED - data[7] serialspeed
# REMOVED - data[8] pttprotocol
# REMOVED - data[9] pttport
# REMOVED - data[10] data_bits
# REMOVED - data[11] stop_bits
# REMOVED - data[12] handshake
# data[13] radiocontrol
# data[14] rigctld_ip
# data[15] rigctld_port
# data[16] send_scatter
# data[17] send_fft
# data[18] low_bandwidth_mode
# data[19] tuning_range_fmin
# data[20] tuning_range_fmax
# data[21] enable FSK
# data[22] tx-audio-level
# data[23] respond_to_cq
# data[24] rx_buffer_size
# data[25] explorer
# data[26] ssid_list
# data[27] auto_tune
# data[28] stats
# TODO: We need to remove 5-12 and adjust the list number for other paramters
# This is only a dirty fix
# data[5] radiocontrol
# data[6] rigctld_ip
# data[7] rigctld_port
# data[8] send_scatter
# data[9] send_fft
# data[10] low_bandwidth_mode
# data[11] tuning_range_fmin
# data[12] tuning_range_fmax
# data[13] enable FSK
# data[14] tx-audio-level
# data[15] respond_to_cq
# data[16] rx_buffer_size
# data[17] explorer
# data[18] ssid_list
# data[19] auto_tune
# data[20] stats
if data[0] == "STARTTNC":
self.log.warning("[DMN] Starting TNC", rig=data[5], port=data[6])
self.start_tnc(data)
# list of parameters, necessary for running subprocess command as a list
options = []
options.append("--port")
options.append(str(static.DAEMONPORT - 1))
# create an additional list entry for parameters not covered by gui
data[50] = int(static.DAEMONPORT - 1)
options.append("--mycall")
options.append(data[1])
options.append("--mygrid")
options.append(data[2])
options.append("--rx")
options.append(data[3])
options.append("--tx")
options.append(data[4])
# if radiocontrol != disabled
# this should hopefully avoid a ton of problems if we are just running in
# disabled mode
if data[5] != "disabled":
options.append("--radiocontrol")
options.append(data[5])
if data[5] == "rigctld":
options.append("--rigctld_ip")
options.append(data[6])
options.append("--rigctld_port")
options.append(data[7])
if data[8] == "True":
options.append("--scatter")
if data[9] == "True":
options.append("--fft")
if data[10] == "True":
options.append("--500hz")
options.append("--tuning_range_fmin")
options.append(data[11])
options.append("--tuning_range_fmax")
options.append(data[12])
# overriding FSK mode
# if data[13] == "True":
# options.append("--fsk")
options.append("--tx-audio-level")
options.append(data[14])
if data[15] == "True":
options.append("--qrv")
options.append("--rx-buffer-size")
options.append(data[16])
if data[17] == "True":
options.append("--explorer")
options.append("--ssid")
for i in data[18]:
options.append(str(i))
if data[19] == "True":
options.append("--tune")
if data[20] == "True":
options.append("--stats")
# safe data to config file
config.write_entire_config(data)
# Try running tnc from binary, else run from source
# This helps running the tnc in a developer environment
try:
command = []
if (getattr(sys, 'frozen', False) or hasattr(sys, "_MEIPASS")) and sys.platform in ["darwin"]:
# If the application is run as a bundle, the PyInstaller bootloader
# extends the sys module by a flag frozen=True and sets the app
# path into variable _MEIPASS'.
application_path = sys._MEIPASS
command.append(application_path + '/freedata-tnc')
else:
if sys.platform in ["linux", "darwin"]:
command.append("./freedata-tnc")
elif sys.platform in ["win32", "win64"]:
command.append("freedata-tnc.exe")
command += options
proc = subprocess.Popen(command)
atexit.register(proc.kill)
self.log.info("[DMN] TNC started", path="binary")
except FileNotFoundError as err1:
self.log.info("[DMN] worker: ", e=err1)
command = []
if sys.platform in ["linux", "darwin"]:
command.append("python3")
elif sys.platform in ["win32", "win64"]:
command.append("python")
command.append("main.py")
command += options
print(command)
proc = subprocess.Popen(command)
atexit.register(proc.kill)
self.log.info("[DMN] TNC started", path="source")
static.TNCPROCESS = proc
static.TNCSTARTED = True
"""
# WE HAVE THIS PART in SOCKET
if data[0] == "STOPTNC":
static.TNCPROCESS.kill()
self.log.warning("[DMN] Stopping TNC")
#os.kill(static.TNCPROCESS, signal.SIGKILL)
static.TNCSTARTED = False
"""
# data[9] radiocontrol
# data[10] rigctld_ip
# data[11] rigctld_port
if data[0] == "TEST_HAMLIB":
radiocontrol = data[1]
rigctld_ip = data[2]
rigctld_port = data[3]
# check how we want to control the radio
if radiocontrol == "direct":
print("direct hamlib support deprecated - not usable anymore")
sys.exit(1)
elif radiocontrol == "rigctl":
print("rigctl support deprecated - not usable anymore")
sys.exit(1)
elif radiocontrol == "rigctld":
import rigctld as rig
else:
import rigdummy as rig
hamlib = rig.radio()
hamlib.open_rig(
rigctld_ip=rigctld_ip,
rigctld_port=rigctld_port,
)
# hamlib_version = rig.hamlib_version
hamlib.set_ptt(True)
pttstate = hamlib.get_ptt()
if pttstate:
self.log.info("[DMN] Hamlib PTT", status="SUCCESS")
response = {"command": "test_hamlib", "result": "SUCCESS"}
else:
self.log.warning("[DMN] Hamlib PTT", status="NO SUCCESS")
response = {"command": "test_hamlib", "result": "NOSUCCESS"}
hamlib.set_ptt(False)
hamlib.close_rig()
jsondata = json.dumps(response)
sock.SOCKET_QUEUE.put(jsondata)
# data[9] radiocontrol
# data[10] rigctld_ip
# data[11] rigctld_port
self.test_hamlib_ptt(data)
except Exception as err1:
self.log.error("[DMN] worker: Exception: ", e=err1)
def test_hamlib_ptt(self, data):
radiocontrol = data[1]
rigctld_ip = data[2]
rigctld_port = data[3]
# check how we want to control the radio
if radiocontrol == "direct":
print("direct hamlib support deprecated - not usable anymore")
sys.exit(1)
elif radiocontrol == "rigctl":
print("rigctl support deprecated - not usable anymore")
sys.exit(1)
elif radiocontrol == "rigctld":
import rigctld as rig
else:
import rigdummy as rig
hamlib = rig.radio()
hamlib.open_rig(
rigctld_ip=rigctld_ip,
rigctld_port=rigctld_port,
)
# hamlib_version = rig.hamlib_version
hamlib.set_ptt(True)
if hamlib.get_ptt():
self.log.info("[DMN] Hamlib PTT", status="SUCCESS")
response = {"command": "test_hamlib", "result": "SUCCESS"}
else:
self.log.warning("[DMN] Hamlib PTT", status="NO SUCCESS")
response = {"command": "test_hamlib", "result": "NOSUCCESS"}
hamlib.set_ptt(False)
hamlib.close_rig()
jsondata = json.dumps(response)
sock.SOCKET_QUEUE.put(jsondata)
def start_tnc(self, data):
self.log.warning("[DMN] Starting TNC", rig=data[5], port=data[6])
# list of parameters, necessary for running subprocess command as a list
options = ["--port", str(static.DAEMONPORT - 1)]
# create an additional list entry for parameters not covered by gui
data[50] = int(static.DAEMONPORT - 1)
options.append("--mycall")
options.extend((data[1], "--mygrid"))
options.extend((data[2], "--rx"))
options.extend((data[3], "--tx"))
options.append(data[4])
# if radiocontrol != disabled
# this should hopefully avoid a ton of problems if we are just running in
# disabled mode
if data[5] != "disabled":
options.append("--radiocontrol")
options.append(data[5])
if data[5] == "rigctld":
options.append("--rigctld_ip")
options.extend((data[6], "--rigctld_port"))
options.append(data[7])
if data[8] == "True":
options.append("--scatter")
if data[9] == "True":
options.append("--fft")
if data[10] == "True":
options.append("--500hz")
options.append("--tuning_range_fmin")
options.extend((data[11], "--tuning_range_fmax"))
options.extend((data[12], "--tx-audio-level"))
options.append(data[14])
if data[15] == "True":
options.append("--qrv")
options.append("--rx-buffer-size")
options.append(data[16])
if data[17] == "True":
options.append("--explorer")
options.append("--ssid")
options.extend(str(i) for i in data[18])
if data[19] == "True":
options.append("--tune")
if data[20] == "True":
options.append("--stats")
# safe data to config file
config.write_entire_config(data)
# Try running tnc from binary, else run from source
# This helps running the tnc in a developer environment
try:
command = []
if (getattr(sys, 'frozen', False) or hasattr(sys, "_MEIPASS")) and sys.platform in ["darwin"]:
# If the application is run as a bundle, the PyInstaller bootloader
# extends the sys module by a flag frozen=True and sets the app
# path into variable _MEIPASS'.
application_path = sys._MEIPASS
command.append(f'{application_path}/freedata-tnc')
elif sys.platform in ["linux", "darwin"]:
command.append("./freedata-tnc")
elif sys.platform in ["win32", "win64"]:
command.append("freedata-tnc.exe")
command += options
proc = subprocess.Popen(command)
atexit.register(proc.kill)
self.log.info("[DMN] TNC started", path="binary")
except FileNotFoundError as err1:
self.log.info("[DMN] worker: ", e=err1)
command = []
if sys.platform in ["linux", "darwin"]:
command.append("python3")
elif sys.platform in ["win32", "win64"]:
command.append("python")
command.append("main.py")
command += options
print(command)
proc = subprocess.Popen(command)
atexit.register(proc.kill)
self.log.info("[DMN] TNC started", path="source")
static.TNCPROCESS = proc
static.TNCSTARTED = True
if __name__ == "__main__":
mainlog = structlog.get_logger(__file__)
# we need to run this on Windows for multiprocessing support

View file

@ -249,7 +249,10 @@ class DATA:
#
# send transmission queued information once
if static.ARQ_STATE or static.IS_CODEC2_TRAFFIC:
self.log.debug(f"[TNC] TX DISPATCHER - waiting with processing command ", arq_state=static.ARQ_STATE)
self.log.debug(
"[TNC] TX DISPATCHER - waiting with processing command ",
arq_state=static.ARQ_STATE,
)
self.send_data_to_socket_queue(
freedata="tnc-message",
@ -635,15 +638,6 @@ 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]
# )
# 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 not in ["BUSY"]:
self.log.warning("[TNC] wrong tnc state - dropping data", arq_state=static.ARQ_STATE, tnc_state=static.TNC_STATE)
@ -701,10 +695,10 @@ class DATA:
# catch possible modem error which leads into false byteorder
# modem possibly decodes too late - data then is pushed to buffer
# which leads into wrong byteorder
# Lets put this in try/except so we are not crashing tnc as its hihgly experimental
# Lets put this in try/except so we are not crashing tnc as its highly experimental
# This might only work for datac1 and datac3
try:
#area_of_interest = (modem.get_bytes_per_frame(self.mode_list[speed_level] - 1) -3) * 2
# area_of_interest = (modem.get_bytes_per_frame(self.mode_list[speed_level] - 1) -3) * 2
if static.RX_FRAME_BUFFER.endswith(temp_burst_buffer[:246]) and len(temp_burst_buffer) >= 246:
self.log.warning(
"[TNC] ARQ | RX | wrong byteorder received - dropping data"
@ -716,7 +710,6 @@ class DATA:
"[TNC] ARQ | RX | wrong byteorder check failed", e=e
)
# if frame buffer ends not with the current frame, we are going to append new data
# if data already exists, we received the frame correctly,
# but the ACK frame didn't receive its destination (ISS)
@ -734,10 +727,9 @@ class DATA:
# temp_burst_buffer --> new data
# search_area --> area where we want to search
#data_mode = self.mode_list[self.speed_level]
#payload_per_frame = modem.get_bytes_per_frame(data_mode) - 2
#search_area = payload_per_frame - 3 # (3 bytes arq frame header)
# data_mode = self.mode_list[self.speed_level]
# payload_per_frame = modem.get_bytes_per_frame(data_mode) - 2
# search_area = payload_per_frame - 3 # (3 bytes arq frame header)
search_area = 510 - 3 # (3 bytes arq frame header)
search_position = len(static.RX_FRAME_BUFFER) - search_area
@ -771,28 +763,11 @@ class DATA:
and data_in.find(self.data_frame_eof) < 0
):
self.frame_received_counter += 1
# try increasing speed level only if we had two successful decodes
if self.frame_received_counter >= 2:
self.frame_received_counter = 0
# make sure new speed level isn't higher than available modes
new_speed_level = min(self.speed_level + 1, len(self.mode_list) - 1)
# check if actual snr is higher than minimum snr for next mode
if static.SNR >= self.snr_list[new_speed_level]:
self.speed_level = new_speed_level
else:
self.log.info("[TNC] ARQ | increasing speed level not possible because of SNR limit",
given_snr=static.SNR,
needed_snr=self.snr_list[new_speed_level]
)
static.ARQ_SPEED_LEVEL = self.speed_level
# Update modes we are listening to
self.set_listening_modes(False, True, self.mode_list[self.speed_level])
self.arq_calculate_speed_level(snr)
# Create and send ACK frame
self.log.info("[TNC] ARQ | RX | SENDING ACK", finished=static.ARQ_SECONDS_UNTIL_FINISH, bytesperminute=static.ARQ_BYTES_PER_MINUTE)
self.log.info("[TNC] ARQ | RX | SENDING ACK", finished=static.ARQ_SECONDS_UNTIL_FINISH,
bytesperminute=static.ARQ_BYTES_PER_MINUTE)
self.send_burst_ack_frame(snr)
# Reset n retries per burst counter
@ -817,7 +792,7 @@ class DATA:
finished=static.ARQ_SECONDS_UNTIL_FINISH,
irs=helpers.bool_to_string(self.is_IRS)
)
elif rx_n_frame_of_burst == rx_n_frames_per_burst - 1:
# We have "Nones" in our rx buffer,
# Check if we received last frame of burst - this is an indicator for missed frames.
@ -834,7 +809,6 @@ class DATA:
self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER)
)
# Should never reach this point
else:
self.log.error(
"[TNC] data_handler: Should not reach this point...",
@ -851,19 +825,7 @@ class DATA:
# get total bytes per transmission information as soon we received a frame with a BOF
if bof_position >= 0:
payload = static.RX_FRAME_BUFFER[
bof_position + len(self.data_frame_bof): eof_position
]
frame_length = int.from_bytes(payload[4:8], "big") # 4:8 4bytes
static.TOTAL_BYTES = frame_length
compression_factor = int.from_bytes(payload[8:9], "big") # 4:8 4bytes
# limit to max value of 255
compression_factor = np.clip(compression_factor, 0, 255)
static.ARQ_COMPRESSION_FACTOR = compression_factor / 10
self.calculate_transfer_rate_rx(
self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER)
)
self.arq_extract_statistics_from_data_frame(bof_position, eof_position)
if (
bof_position >= 0
and eof_position > 0
@ -893,131 +855,7 @@ class DATA:
# Check if data_frame_crc is equal with received crc
if data_frame_crc == data_frame_crc_received:
# transmittion duration
duration = time.time() - self.rx_start_of_transmission
self.calculate_transfer_rate_rx(
self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER)
)
self.log.info("[TNC] ARQ | RX | DATA FRAME SUCCESSFULLY RECEIVED", nacks=self.frame_nack_counter,bytesperminute=static.ARQ_BYTES_PER_MINUTE, total_bytes=static.TOTAL_BYTES, duration=duration)
# Decompress the data frame
data_frame_decompressed = lzma.decompress(data_frame)
static.ARQ_COMPRESSION_FACTOR = len(data_frame_decompressed) / len(
data_frame
)
data_frame = data_frame_decompressed
self.transmission_uuid = str(uuid.uuid4())
timestamp = int(time.time())
# Re-code data_frame in base64, UTF-8 for JSON UI communication.
base64_data = base64.b64encode(data_frame).decode("UTF-8")
# check if RX_BUFFER isn't full
if not RX_BUFFER.full():
# make sure we have always the correct buffer size
RX_BUFFER.maxsize = int(static.RX_BUFFER_SIZE)
else:
# if full, free space by getting an item
self.log.info(
"[TNC] ARQ | RX | RX_BUFFER FULL - dropping old data",
buffer_size=RX_BUFFER.qsize(),
maxsize=int(static.RX_BUFFER_SIZE)
)
RX_BUFFER.get()
# add item to RX_BUFFER
self.log.info(
"[TNC] ARQ | RX | saving data to rx buffer",
buffer_size=RX_BUFFER.qsize() + 1,
maxsize=RX_BUFFER.maxsize
)
try:
RX_BUFFER.put(
[
self.transmission_uuid,
timestamp,
static.DXCALLSIGN,
static.DXGRID,
base64_data,
]
)
except Exception as e:
# File "/usr/lib/python3.7/queue.py", line 133, in put
# if self.maxsize > 0
# TypeError: '>' not supported between instances of 'str' and 'int'
#
# Occurs on Raspberry Pi and Python 3.7
self.log.error(
"[TNC] ARQ | RX | error occurred when saving data!",
e=e,
uuid=self.transmission_uuid,
timestamp=timestamp,
dxcall=static.DXCALLSIGN,
dxgrid=static.DXGRID,
data=base64_data
)
if static.ARQ_SAVE_TO_FOLDER:
try:
self.save_data_to_folder(
self.transmission_uuid,
timestamp,
mycallsign,
static.DXCALLSIGN,
static.DXGRID,
data_frame
)
except Exception as e:
self.log.error(
"[TNC] ARQ | RX | can't save file to folder",
e=e,
uuid=self.transmission_uuid,
timestamp=timestamp,
dxcall=static.DXCALLSIGN,
dxgrid=static.DXGRID,
data=base64_data
)
self.send_data_to_socket_queue(
freedata="tnc-message",
arq="transmission",
status="received",
uuid=self.transmission_uuid,
timestamp=timestamp,
mycallsign=str(mycallsign, "UTF-8"),
dxcallsign=str(static.DXCALLSIGN, "UTF-8"),
dxgrid=str(static.DXGRID, "UTF-8"),
data=base64_data,
irs=helpers.bool_to_string(self.is_IRS)
)
if static.ENABLE_STATS:
duration = time.time() - self.rx_start_of_transmission
self.stats.push(frame_nack_counter=self.frame_nack_counter, status="received", duration=duration)
self.log.info(
"[TNC] ARQ | RX | SENDING DATA FRAME ACK",
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(
self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER)
)
self.log.info(
"[TNC] | RX | DATACHANNEL ["
+ str(self.mycallsign, "UTF-8")
+ "]<< >>["
+ str(static.DXCALLSIGN, "UTF-8")
+ "]",
snr=snr,
)
self.arq_process_received_data_frame(data_frame, snr)
else:
self.send_data_to_socket_queue(
freedata="tnc-message",
@ -1055,6 +893,168 @@ class DATA:
# Finally cleanup our buffers and states,
self.arq_cleanup()
def arq_extract_statistics_from_data_frame(self, bof_position, eof_position):
payload = static.RX_FRAME_BUFFER[
bof_position + len(self.data_frame_bof): eof_position
]
frame_length = int.from_bytes(payload[4:8], "big") # 4:8 4bytes
static.TOTAL_BYTES = frame_length
compression_factor = int.from_bytes(payload[8:9], "big") # 4:8 4bytes
# limit to max value of 255
compression_factor = np.clip(compression_factor, 0, 255)
static.ARQ_COMPRESSION_FACTOR = compression_factor / 10
self.calculate_transfer_rate_rx(
self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER)
)
def arq_calculate_speed_level(self, snr):
self.frame_received_counter += 1
# try increasing speed level only if we had two successful decodes
if self.frame_received_counter >= 2:
self.frame_received_counter = 0
# make sure new speed level isn't higher than available modes
new_speed_level = min(self.speed_level + 1, len(self.mode_list) - 1)
# check if actual snr is higher than minimum snr for next mode
if static.SNR >= self.snr_list[new_speed_level]:
self.speed_level = new_speed_level
else:
self.log.info("[TNC] ARQ | increasing speed level not possible because of SNR limit",
given_snr=static.SNR,
needed_snr=self.snr_list[new_speed_level]
)
static.ARQ_SPEED_LEVEL = self.speed_level
# Update modes we are listening to
self.set_listening_modes(False, True, self.mode_list[self.speed_level])
def arq_process_received_data_frame(self, data_frame, snr):
"""
"""
# transmittion duration
duration = time.time() - self.rx_start_of_transmission
self.calculate_transfer_rate_rx(
self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER)
)
self.log.info("[TNC] ARQ | RX | DATA FRAME SUCCESSFULLY RECEIVED", nacks=self.frame_nack_counter,
bytesperminute=static.ARQ_BYTES_PER_MINUTE, total_bytes=static.TOTAL_BYTES, duration=duration)
# Decompress the data frame
data_frame_decompressed = lzma.decompress(data_frame)
static.ARQ_COMPRESSION_FACTOR = len(data_frame_decompressed) / len(
data_frame
)
data_frame = data_frame_decompressed
self.transmission_uuid = str(uuid.uuid4())
timestamp = int(time.time())
# Re-code data_frame in base64, UTF-8 for JSON UI communication.
base64_data = base64.b64encode(data_frame).decode("UTF-8")
# check if RX_BUFFER isn't full
if not RX_BUFFER.full():
# make sure we have always the correct buffer size
RX_BUFFER.maxsize = int(static.RX_BUFFER_SIZE)
else:
# if full, free space by getting an item
self.log.info(
"[TNC] ARQ | RX | RX_BUFFER FULL - dropping old data",
buffer_size=RX_BUFFER.qsize(),
maxsize=int(static.RX_BUFFER_SIZE)
)
RX_BUFFER.get()
# add item to RX_BUFFER
self.log.info(
"[TNC] ARQ | RX | saving data to rx buffer",
buffer_size=RX_BUFFER.qsize() + 1,
maxsize=RX_BUFFER.maxsize
)
try:
RX_BUFFER.put(
[
self.transmission_uuid,
timestamp,
static.DXCALLSIGN,
static.DXGRID,
base64_data,
]
)
except Exception as e:
# File "/usr/lib/python3.7/queue.py", line 133, in put
# if self.maxsize > 0
# TypeError: '>' not supported between instances of 'str' and 'int'
#
# Occurs on Raspberry Pi and Python 3.7
self.log.error(
"[TNC] ARQ | RX | error occurred when saving data!",
e=e,
uuid=self.transmission_uuid,
timestamp=timestamp,
dxcall=static.DXCALLSIGN,
dxgrid=static.DXGRID,
data=base64_data
)
if static.ARQ_SAVE_TO_FOLDER:
try:
self.save_data_to_folder(
self.transmission_uuid,
timestamp,
self.mycallsign,
static.DXCALLSIGN,
static.DXGRID,
data_frame
)
except Exception as e:
self.log.error(
"[TNC] ARQ | RX | can't save file to folder",
e=e,
uuid=self.transmission_uuid,
timestamp=timestamp,
dxcall=static.DXCALLSIGN,
dxgrid=static.DXGRID,
data=base64_data
)
self.send_data_to_socket_queue(
freedata="tnc-message",
arq="transmission",
status="received",
uuid=self.transmission_uuid,
timestamp=timestamp,
mycallsign=str(self.mycallsign, "UTF-8"),
dxcallsign=str(static.DXCALLSIGN, "UTF-8"),
dxgrid=str(static.DXGRID, "UTF-8"),
data=base64_data,
irs=helpers.bool_to_string(self.is_IRS)
)
if static.ENABLE_STATS:
duration = time.time() - self.rx_start_of_transmission
self.stats.push(frame_nack_counter=self.frame_nack_counter, status="received", duration=duration)
self.log.info(
"[TNC] ARQ | RX | SENDING DATA FRAME ACK")
self.send_data_ack_frame(snr)
# Update statistics AFTER the frame ACK is sent
self.calculate_transfer_rate_rx(
self.rx_start_of_transmission, len(static.RX_FRAME_BUFFER)
)
self.log.info(
"[TNC] | RX | DATACHANNEL ["
+ str(self.mycallsign, "UTF-8")
+ "]<< >>["
+ str(static.DXCALLSIGN, "UTF-8")
+ "]",
snr=snr,
)
def arq_transmit(self, data_out: bytes, mode: int, n_frames_per_burst: int):
"""
Transmit ARQ frame
@ -1065,8 +1065,6 @@ class DATA:
n_frames_per_burst:int:
"""
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
@ -1075,9 +1073,9 @@ class 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
# save len of data_out to TOTAL_BYTES for our statistics --> kBytes
# static.TOTAL_BYTES = round(len(data_out) / 1024, 2)
# save len of data_out to TOTAL_BYTES for our statistics
static.TOTAL_BYTES = len(data_out)
self.arq_file_transfer = True
frame_total_size = len(data_out).to_bytes(4, byteorder="big")
# Compress data frame
@ -1132,21 +1130,6 @@ class DATA:
while not self.data_frame_ack_received and static.ARQ_STATE:
# we have self.tx_n_max_retries_per_burst attempts for sending a burst
for self.tx_n_retry_of_burst in range(self.tx_n_max_retries_per_burst):
# 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
# every 2nd retry which fails, decreases speedlevel by 1.
# as soon as we received an ACK for the current burst, speed_level will increase
# by 1.
# The intent is to optimize speed by adapting to the current RF conditions.
# if not self.tx_n_retry_of_burst % 2 and self.tx_n_retry_of_burst > 0:
# self.speed_level = max(self.speed_level - 1, 0)
# if self.tx_n_retry_of_burst <= 1:
# self.speed_level += 1
# self.speed_level = max(self.speed_level + 1, len(self.mode_list) - 1)
# Bound speed level to:
# - minimum of either the speed or the length of mode list - 1
# - maximum of either the speed or zero
@ -1166,9 +1149,6 @@ class DATA:
# Payload information
payload_per_frame = modem.get_bytes_per_frame(data_mode) - 2
# Tempbuffer list for storing our data frames
tempbuffer = []
# Append data frames with n_frames_per_burst to tempbuffer
# TODO: this part needs a complete rewrite!
# n_frames_per_burst = 1 is working
@ -1194,9 +1174,7 @@ class DATA:
)
frame = arqheader + extended_data_out
# Append frame to tempbuffer for transmission
tempbuffer.append(frame)
tempbuffer = [frame]
self.log.debug("[TNC] tempbuffer:", tempbuffer=tempbuffer)
self.log.info(
"[TNC] ARQ | TX | FRAMES",
@ -1209,18 +1187,12 @@ class DATA:
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() + 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):
# threading.Event().wait(0.01)
# burstacktimeout = time.time() + self.burst_ack_timeout_seconds + 100
while static.ARQ_STATE and not (
self.burst_ack
or self.burst_nack
or self.rpt_request_received
or self.data_frame_ack_received
while (
static.ARQ_STATE
and not self.burst_ack
and not self.burst_nack
and not self.rpt_request_received
and not self.data_frame_ack_received
):
threading.Event().wait(0.01)
@ -1236,20 +1208,15 @@ class DATA:
if self.burst_nack:
self.burst_nack = False # reset nack state
# not yet implemented
if self.rpt_request_received:
pass
if self.data_frame_ack_received:
self.log.debug(
"[TNC] arq_transmit: Received FRAME ACK. Sending next chunk."
"[TNC] arq_transmit: Received FRAME ACK. Braking retry loop."
)
break # break retry loop
# We need this part for leaving the repeat loop
# static.ARQ_STATE == "DATA" --> when stopping transmission manually
if not static.ARQ_STATE:
# print("not ready for data...leaving loop....")
self.log.debug(
"[TNC] arq_transmit: ARQ State changed to FALSE. Breaking retry loop."
)
@ -1265,7 +1232,6 @@ class DATA:
maxretries=self.tx_n_max_retries_per_burst,
overflows=static.BUFFER_OVERFLOW_COUNTER,
)
# End of FOR loop
# update buffer position
bufferposition = bufferposition_end
@ -1296,66 +1262,77 @@ class DATA:
if self.data_frame_ack_received and bufferposition > len(data_out):
self.log.debug("[TNC] arq_tx: Last fragment sent and acknowledged.")
break
# GOING TO NEXT ITERATION
# GOING TO NEXT ITERATION
if self.data_frame_ack_received:
# we need to wait until sending "transmitted" state
# gui database is too slow for handling this within 0.001 seconds
# so let's sleep a little
threading.Event().wait(0.2)
self.send_data_to_socket_queue(
freedata="tnc-message",
arq="transmission",
status="transmitted",
uuid=self.transmission_uuid,
percent=static.ARQ_TRANSMISSION_PERCENT,
bytesperminute=static.ARQ_BYTES_PER_MINUTE,
compression=static.ARQ_COMPRESSION_FACTOR,
finished=static.ARQ_SECONDS_UNTIL_FINISH,
mycallsign=str(self.mycallsign, 'UTF-8'),
dxcallsign=str(self.dxcallsign, 'UTF-8'),
irs=helpers.bool_to_string(self.is_IRS)
)
self.log.info(
"[TNC] ARQ | TX | DATA TRANSMITTED!",
BytesPerMinute=static.ARQ_BYTES_PER_MINUTE,
total_bytes=static.TOTAL_BYTES,
BitsPerSecond=static.ARQ_BITS_PER_SECOND,
overflows=static.BUFFER_OVERFLOW_COUNTER,
)
# finally do an arq cleanup
self.arq_cleanup()
self.arq_transmit_success()
else:
self.send_data_to_socket_queue(
freedata="tnc-message",
arq="transmission",
status="failed",
uuid=self.transmission_uuid,
percent=static.ARQ_TRANSMISSION_PERCENT,
bytesperminute=static.ARQ_BYTES_PER_MINUTE,
compression=static.ARQ_COMPRESSION_FACTOR,
mycallsign=str(self.mycallsign, 'UTF-8'),
dxcallsign=str(self.dxcallsign, 'UTF-8'),
irs=helpers.bool_to_string(self.is_IRS)
)
self.log.info(
"[TNC] ARQ | TX | TRANSMISSION FAILED OR TIME OUT!",
overflows=static.BUFFER_OVERFLOW_COUNTER,
)
self.stop_transmission()
self.arq_transmit_failed()
if TESTMODE:
# Quit after transmission
self.log.debug("[TNC] TESTMODE: arq_transmit exiting.")
sys.exit(0)
def arq_transmit_success(self):
"""
will be called if we successfully transmitted all of queued data
"""
# we need to wait until sending "transmitted" state
# gui database is too slow for handling this within 0.001 seconds
# so let's sleep a little
threading.Event().wait(0.2)
self.send_data_to_socket_queue(
freedata="tnc-message",
arq="transmission",
status="transmitted",
uuid=self.transmission_uuid,
percent=static.ARQ_TRANSMISSION_PERCENT,
bytesperminute=static.ARQ_BYTES_PER_MINUTE,
compression=static.ARQ_COMPRESSION_FACTOR,
finished=static.ARQ_SECONDS_UNTIL_FINISH,
mycallsign=str(self.mycallsign, 'UTF-8'),
dxcallsign=str(self.dxcallsign, 'UTF-8'),
irs=helpers.bool_to_string(self.is_IRS)
)
self.log.info(
"[TNC] ARQ | TX | DATA TRANSMITTED!",
BytesPerMinute=static.ARQ_BYTES_PER_MINUTE,
total_bytes=static.TOTAL_BYTES,
BitsPerSecond=static.ARQ_BITS_PER_SECOND,
overflows=static.BUFFER_OVERFLOW_COUNTER,
)
# finally do an arq cleanup
self.arq_cleanup()
def arq_transmit_failed(self):
"""
will be called if we not successfully transmitted all of queued data
"""
self.send_data_to_socket_queue(
freedata="tnc-message",
arq="transmission",
status="failed",
uuid=self.transmission_uuid,
percent=static.ARQ_TRANSMISSION_PERCENT,
bytesperminute=static.ARQ_BYTES_PER_MINUTE,
compression=static.ARQ_COMPRESSION_FACTOR,
mycallsign=str(self.mycallsign, 'UTF-8'),
dxcallsign=str(self.dxcallsign, 'UTF-8'),
irs=helpers.bool_to_string(self.is_IRS)
)
self.log.info(
"[TNC] ARQ | TX | TRANSMISSION FAILED OR TIME OUT!",
overflows=static.BUFFER_OVERFLOW_COUNTER,
)
self.stop_transmission()
def burst_ack_nack_received(self, data_in: bytes) -> None:
"""
Received an ACK/NACK for a transmitted frame, keep track and
@ -1390,6 +1367,8 @@ class DATA:
self.burst_nack_counter = 0
# Reset n retries per burst counter
self.n_retries_per_burst = 0
self.burst_ack_snr = helpers.snr_from_bytes(data_in[2:3])
else:
# Decrease speed level if we received a burst nack
# self.speed_level = max(self.speed_level - 1, 0)
@ -1397,7 +1376,7 @@ class DATA:
self.burst_nack = True
# Increment burst nack counter
self.burst_nack_counter += 1
desc = "nack"
self.burst_ack_snr = 'NaN'
# Update data_channel timestamp
self.data_channel_last_received = int(time.time())
@ -1408,12 +1387,6 @@ class DATA:
self.speed_level = int.from_bytes(bytes(data_in[3:4]), "big")
static.ARQ_SPEED_LEVEL = self.speed_level
#self.log.debug(
# f"[TNC] burst_{desc}_received:",
# speed_level=self.speed_level,
# c2_mode=FREEDV_MODE(self.mode_list[self.speed_level]).name,
#)
def frame_ack_received(
self, data_in: bytes # pylint: disable=unused-argument
) -> None:
@ -1491,28 +1464,28 @@ class DATA:
"""
# Only process data if we are in ARQ and BUSY state
if static.ARQ_STATE and static.TNC_STATE == "BUSY":
static.DXGRID = b'------'
helpers.add_to_heard_stations(
static.DXCALLSIGN,
static.DXGRID,
"DATA-CHANNEL",
static.SNR,
static.FREQ_OFFSET,
static.HAMLIB_FREQUENCY,
)
if not static.ARQ_STATE or static.TNC_STATE != "BUSY":
return
static.DXGRID = b'------'
helpers.add_to_heard_stations(
static.DXCALLSIGN,
static.DXGRID,
"DATA-CHANNEL",
static.SNR,
static.FREQ_OFFSET,
static.HAMLIB_FREQUENCY,
)
self.rpt_request_received = True
# Update data_channel timestamp
self.data_channel_last_received = int(time.time())
self.rpt_request_buffer = []
self.rpt_request_received = True
# Update data_channel timestamp
self.data_channel_last_received = int(time.time())
self.rpt_request_buffer = []
missing_area = bytes(data_in[3:12]) # 1:9
missing_area = bytes(data_in[3:12]) # 1:9
for i in range(0, 6, 2):
if not missing_area[i: i + 2].endswith(b"\x00\x00"):
missing = missing_area[i: i + 2]
self.rpt_request_buffer.insert(0, missing)
for i in range(0, 6, 2):
if not missing_area[i: i + 2].endswith(b"\x00\x00"):
self.rpt_request_buffer.insert(0, missing_area[i: i + 2])
############################################################################################################
# ARQ SESSION HANDLER
@ -1800,7 +1773,6 @@ class DATA:
data_in:bytes:
"""
print(static.ARQ_SESSION_STATE)
# We've arrived here from process_data which already checked that the frame
# is intended for this station.
# Close the session if the CRC matches the remote station in static.
@ -2166,7 +2138,7 @@ class DATA:
self.mode_list = self.mode_list_high_bw
self.time_list = self.time_list_high_bw
self.snr_list = self.snr_list_high_bw
elif frametype == FR_TYPE.ARQ_DC_OPEN_W.value and static.LOW_BANDWIDTH_MODE:
elif frametype == FR_TYPE.ARQ_DC_OPEN_W.value:
# ISS(w) <-> IRS(n)
constellation = "ISS(w) <-> IRS(n)"
self.received_LOW_BANDWIDTH_MODE = False
@ -2180,7 +2152,7 @@ class DATA:
self.mode_list = self.mode_list_low_bw
self.time_list = self.time_list_low_bw
self.snr_list = self.snr_list_low_bw
elif frametype == FR_TYPE.ARQ_DC_OPEN_N.value and static.LOW_BANDWIDTH_MODE:
elif frametype == FR_TYPE.ARQ_DC_OPEN_N.value:
# ISS(n) <-> IRS(n)
constellation = "ISS(n) <-> IRS(n)"
self.received_LOW_BANDWIDTH_MODE = True
@ -2195,7 +2167,7 @@ class DATA:
self.snr_list = self.snr_list_low_bw
# get mode which fits to given SNR
# initially set speed_level 0 in case of really bad SNR and no matching mode
# initially set speed_level 0 in case of bad SNR and no matching mode
self.speed_level = 0
for i in range(len(self.mode_list)):
if static.SNR >= self.snr_list[i]:
@ -2236,7 +2208,7 @@ class DATA:
# Reset data_channel/burst timestamps
self.data_channel_last_received = int(time.time())
self.burst_last_received = int(time.time() + 6) # we might need some more time so lets increase this
self.burst_last_received = int(time.time() + 6) # we might need some more time so lets increase this
# Set ARQ State AFTER resetting timeouts
# this avoids timeouts starting too early
@ -2257,8 +2229,6 @@ class DATA:
connection_frame[:1] = frametype
connection_frame[1:2] = self.session_id
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], c2_mode=FREEDV_MODE.datac0.value, copies=1, repeat_delay=0)
@ -2287,7 +2257,7 @@ class DATA:
# Reset data_channel/burst timestamps once again for avoiding running into timeout
self.data_channel_last_received = int(time.time())
self.burst_last_received = int(time.time() + 6) # we might need some more time so lets increase this
self.burst_last_received = int(time.time() + 6) # we might need some more time so lets increase this
def arq_received_channel_is_open(self, data_in: bytes) -> None:
"""
@ -2402,8 +2372,8 @@ class DATA:
ping_frame[4:7] = helpers.get_crc_24(mycallsign)
ping_frame[7:13] = helpers.callsign_to_bytes(mycallsign)
self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK)
if static.ENABLE_FSK:
self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK)
self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value)
else:
self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.datac0.value)
@ -2458,17 +2428,25 @@ class DATA:
snr=str(static.SNR),
)
if static.RESPOND_TO_CALL:
ping_frame = bytearray(self.length_sig0_frame)
ping_frame[:1] = bytes([FR_TYPE.PING_ACK.value])
ping_frame[1:4] = static.DXCALLSIGN_CRC
ping_frame[4:7] = static.MYCALLSIGN_CRC
ping_frame[7:11] = helpers.encode_grid(static.MYGRID.decode("UTF-8"))
ping_frame[13:14] = helpers.snr_to_bytes(static.SNR)
self.transmit_ping_ack()
if static.ENABLE_FSK:
self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value)
else:
self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.datac0.value)
def transmit_ping_ack(self):
"""
transmit a ping ack frame
called by def received_ping
"""
ping_frame = bytearray(self.length_sig0_frame)
ping_frame[:1] = bytes([FR_TYPE.PING_ACK.value])
ping_frame[1:4] = static.DXCALLSIGN_CRC
ping_frame[4:7] = static.MYCALLSIGN_CRC
ping_frame[7:11] = helpers.encode_grid(static.MYGRID.decode("UTF-8"))
ping_frame[13:14] = helpers.snr_to_bytes(static.SNR)
if static.ENABLE_FSK:
self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value)
else:
self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.datac0.value)
def received_ping_ack(self, data_in: bytes) -> None:
"""
@ -2611,9 +2589,9 @@ class DATA:
beacon_frame[:1] = bytes([FR_TYPE.BEACON.value])
beacon_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign)
beacon_frame[7:11] = helpers.encode_grid(static.MYGRID.decode("UTF-8"))
self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK)
if static.ENABLE_FSK:
self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK)
self.enqueue_frame_for_tx(
[beacon_frame],
c2_mode=FREEDV_MODE.fsk_ldpc_0.value,
@ -2691,10 +2669,10 @@ class DATA:
cq_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign)
cq_frame[7:11] = helpers.encode_grid(static.MYGRID.decode("UTF-8"))
self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK)
self.log.debug("[TNC] CQ Frame:", data=[cq_frame])
if static.ENABLE_FSK:
self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK)
self.enqueue_frame_for_tx([cq_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value)
else:
self.enqueue_frame_for_tx([cq_frame], c2_mode=FREEDV_MODE.datac0.value, copies=1, repeat_delay=0)
@ -2766,7 +2744,6 @@ class DATA:
qrv_frame[7:11] = helpers.encode_grid(static.MYGRID.decode("UTF-8"))
qrv_frame[11:12] = helpers.snr_to_bytes(static.SNR)
if static.ENABLE_FSK:
self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK)
self.enqueue_frame_for_tx([qrv_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value)
@ -3012,6 +2989,7 @@ class DATA:
self.data_frame_ack_received = state
def set_listening_modes(self, enable_sig0: bool, enable_sig1: bool, mode: int) -> None:
# sourcery skip: extract-duplicate-method
"""
Function for setting the data modes we are listening to for saving cpu power
@ -3087,20 +3065,14 @@ class DATA:
print(time.time() - (self.burst_last_received + self.time_list[self.speed_level]))
print("-----------------------")
if modem_error_state:
self.log.warning(
"[TNC] Decoding Error",
attempt=self.n_retries_per_burst,
max_attempts=self.rx_n_max_retries_per_burst,
speed_level=self.speed_level,
)
else:
self.log.warning(
"[TNC] Burst timeout",
attempt=self.n_retries_per_burst,
max_attempts=self.rx_n_max_retries_per_burst,
speed_level=self.speed_level,
)
self.log.warning(
"[TNC] Burst decoding error or timeout",
attempt=self.n_retries_per_burst,
max_attempts=self.rx_n_max_retries_per_burst,
speed_level=self.speed_level,
modem_error_state=modem_error_state
)
# reset self.burst_last_received
self.burst_last_received = time.time() + self.time_list[self.speed_level]

View file

@ -537,17 +537,22 @@ class RF:
x = np.frombuffer(txbuffer, dtype=np.int16)
if static.AUDIO_AUTO_TUNE:
if static.HAMLIB_ALC == 0.0:
static.TX_AUDIO_LEVEL = static.TX_AUDIO_LEVEL + 30
elif 0.0 < static.HAMLIB_ALC <= 0.8:
print("0.001 > static.HAMLIB_ALC <= 0.8")
static.TX_AUDIO_LEVEL = static.TX_AUDIO_LEVEL + 20
self.log.debug("[MDM] AUDIO TUNE", audio_level=str(static.TX_AUDIO_LEVEL), alc_level=str(static.HAMLIB_ALC))
elif 0.8 < static.HAMLIB_ALC < 0.99:
print("0.8 > static.HAMLIB_ALC <= 0.99")
elif 0.0 < static.HAMLIB_ALC <= 0.1:
print("0.0 < static.HAMLIB_ALC <= 0.1")
static.TX_AUDIO_LEVEL = static.TX_AUDIO_LEVEL + 2
self.log.debug("[MDM] AUDIO TUNE", audio_level=str(static.TX_AUDIO_LEVEL), alc_level=str(static.HAMLIB_ALC))
elif 1.0 <= static.HAMLIB_ALC:
static.TX_AUDIO_LEVEL = static.TX_AUDIO_LEVEL - 2
elif 0.1 < static.HAMLIB_ALC < 0.2:
print("0.1 < static.HAMLIB_ALC < 0.2")
static.TX_AUDIO_LEVEL = static.TX_AUDIO_LEVEL
self.log.debug("[MDM] AUDIO TUNE", audio_level=str(static.TX_AUDIO_LEVEL), alc_level=str(static.HAMLIB_ALC))
elif 0.2 < static.HAMLIB_ALC < 0.99:
print("0.2 < static.HAMLIB_ALC < 0.99")
static.TX_AUDIO_LEVEL = static.TX_AUDIO_LEVEL - 20
self.log.debug("[MDM] AUDIO TUNE", audio_level=str(static.TX_AUDIO_LEVEL), alc_level=str(static.HAMLIB_ALC))
elif 1.0 >=static.HAMLIB_ALC:
print("1.0 >= static.HAMLIB_ALC")
static.TX_AUDIO_LEVEL = static.TX_AUDIO_LEVEL - 40
self.log.debug("[MDM] AUDIO TUNE", audio_level=str(static.TX_AUDIO_LEVEL), alc_level=str(static.HAMLIB_ALC))
else:
self.log.debug("[MDM] AUDIO TUNE", audio_level=str(static.TX_AUDIO_LEVEL), alc_level=str(static.HAMLIB_ALC))

File diff suppressed because it is too large Load diff

View file

@ -22,16 +22,10 @@ class stats():
self.explorer_url = "https://api.freedata.app/stats.php"
def push(self, frame_nack_counter, status, duration):
if status in ["crc_error"]:
crcerror = True
else:
crcerror = False
crcerror = status in ["crc_error"]
# get avg snr
try:
snr_raw = []
for item in static.SPEED_LIST:
snr_raw.append(item["snr"])
snr_raw = [item["snr"] for item in static.SPEED_LIST]
avg_snr = round(sum(snr_raw) / len(snr_raw), 2 )
except Exception:
avg_snr = 0

View file

View file

@ -6,6 +6,7 @@ Created on Fri Dec 11 21:53:35 2020
@author: parallels
"""
import socket
import sys
import argparse
@ -13,7 +14,7 @@ import time
# --------------------------------------------GET PARAMETER INPUTS
parser = argparse.ArgumentParser(description='Simons TEST TNC')
parser.add_argument('--port', dest="socket_port", default=3000, help="Set the port, the socket is listening on.", type=int)
parser.add_argument('--port', dest="socket_port", default=3000, help="Set the port, the socket is listening on.", type=int)
parser.add_argument('--data', dest="data", default=False, help="data", type=str)
args = parser.parse_args()
@ -29,6 +30,6 @@ while True:
sock.connect((ip, port))
sock.sendall(bytes(message, 'utf-8') + b'\n')
response = str(sock.recv(1024), 'utf-8')
print("CMD: {}".format(response))
print(f"CMD: {response}")
False
break