From dff5b8e3cd6ef471467f42650bdeca741a3ca708 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sat, 28 Oct 2023 22:37:15 +0200 Subject: [PATCH 01/10] improved channel opening --- modem/data_handler.py | 221 ++++++++++++++++++++++-------------------- modem/static.py | 4 +- 2 files changed, 117 insertions(+), 108 deletions(-) diff --git a/modem/data_handler.py b/modem/data_handler.py index 6ed9d3cb..43a59f42 100644 --- a/modem/data_handler.py +++ b/modem/data_handler.py @@ -110,7 +110,6 @@ class DATA: self.received_LOW_BANDWIDTH_MODE = False self.data_channel_max_retries = 15 - self.datachannel_timeout = False # -------------- AVAILABLE MODES START----------- # IMPORTANT: LISTS MUST BE OF EQUAL LENGTH @@ -182,6 +181,8 @@ class DATA: self.data_frame_ack_timeout_seconds = 4.5 # timeout for data frame acknowledges self.rpt_ack_timeout_seconds = 4.5 # timeout for rpt frame acknowledges self.transmission_timeout = 180 # transmission timeout in seconds + self.channel_busy_timeout = 2 # time how long we want to wait until channel busy state overrides + self.datachannel_opening_interval = self.duration_sig1_frame + self.channel_busy_timeout + 1 # time between attempts when opening data channel # Dictionary of functions and log messages used in process_data # instead of a long series of if-elif-else statements. @@ -1742,8 +1743,6 @@ class DATA: Station.dxcallsign = self.dxcallsign Station.dxcallsign_crc = helpers.get_crc_24(self.dxcallsign) - # TODO we need to check this, maybe placing it to class init - self.datachannel_timeout = False self.log.info( "[Modem] SESSION [" + str(self.mycallsign, "UTF-8") @@ -2162,114 +2161,19 @@ class DATA: if ARQ.arq_session: threading.Event().wait(2.5) - self.datachannel_timeout = False - - # we need to compress data for getting a compression factor. - # so we are compressing twice. This is not that nice and maybe there is another way - # for calculating transmission statistics - # ARQ.arq_compression_factor = len(data_out) / len(lzma.compress(data_out)) + # init arq state event + ARQ.arq_state_event = threading.Event() + # finally start the channel opening procedure self.arq_open_data_channel(mycallsign) - # wait until data channel is open - while not ARQ.arq_state and not self.datachannel_timeout and Modem.modem_state in ["BUSY"]: - threading.Event().wait(0.01) - - if ARQ.arq_state: + # if data channel is open, return true else false + if ARQ.arq_state_event.is_set(): + # start arq transmission self.arq_transmit(data_out, hmac_salt) return True - return False - - def arq_open_data_channel( - self, mycallsign - ) -> bool: - """ - Open an ARQ data channel. - - Args: - mycallsign:bytes: - - Returns: - True if the data channel was opened successfully - False if the data channel failed to open - """ - self.is_IRS = False - - # init a new random session id if we are not in an arq session - if not ARQ.arq_session: - self.session_id = np.random.bytes(1) - - # Update data_channel timestamp - self.data_channel_last_received = int(time.time()) - - if Modem.low_bandwidth_mode: - frametype = bytes([FR_TYPE.ARQ_DC_OPEN_N.value]) - self.log.debug("[Modem] Requesting low bandwidth mode") - else: - frametype = bytes([FR_TYPE.ARQ_DC_OPEN_W.value]) - self.log.debug("[Modem] Requesting high bandwidth mode") - - connection_frame = bytearray(self.length_sig0_frame) - connection_frame[:1] = frametype - connection_frame[1:4] = Station.dxcallsign_crc - connection_frame[4:7] = Station.mycallsign_crc - connection_frame[7:13] = helpers.callsign_to_bytes(mycallsign) - connection_frame[13:14] = self.session_id - - while not ARQ.arq_state: - threading.Event().wait(0.01) - for attempt in range(self.data_channel_max_retries): - - self.send_data_to_socket_queue( - freedata="modem-message", - arq="transmission", - status="opening", - mycallsign=str(mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS) - ) - - self.log.info( - "[Modem] ARQ | DATA | TX | [" - + str(mycallsign, "UTF-8") - + "]>> <<[" - + str(self.dxcallsign, "UTF-8") - + "]", - attempt=f"{str(attempt + 1)}/{str(self.data_channel_max_retries)}", - ) - - # Let's check if we have a busy channel and if we are not in a running arq session. - if ModemParam.channel_busy and not ARQ.arq_state: - self.log.warning("[Modem] Channel busy, waiting until free...") - self.send_data_to_socket_queue( - freedata="modem-message", - arq="transmission", - status="waiting", - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS) - ) - - # wait while timeout not reached and our busy state is busy - channel_busy_timeout = time.time() + 5 - while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): - threading.Event().wait(0.01) - - self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) - - timeout = time.time() + self.duration_sig1_frame * 3 + (ModemParam.tx_delay / 1000 * 2) - while time.time() < timeout: - threading.Event().wait(0.01) - # Stop waiting if data channel is opened - if ARQ.arq_state: - return True - if Modem.modem_state in ["IDLE"]: - return False - - # `data_channel_max_retries` attempts have been sent. Aborting attempt & cleaning up - self.log.debug( "[Modem] arq_open_data_channel:", transmission_uuid=self.transmission_uuid ) @@ -2297,16 +2201,92 @@ class DATA: + str(self.dxcallsign, "UTF-8") + "]" ) - self.datachannel_timeout = True # Attempt to clean up the far-side, if it received the # open_session frame and can still hear us. self.close_session() + # otherwise return false return False - # Shouldn't get here... - return True + def arq_open_data_channel( + self, mycallsign + ) -> bool: + """ + Open an ARQ data channel. + + Args: + mycallsign:bytes: + + Returns: + True if the data channel was opened successfully + False if the data channel failed to open + """ + # set IRS indicator to false, because we are IRS + self.is_IRS = False + + # init a new random session id if we are not in an arq session + if not ARQ.arq_session: + self.session_id = np.random.bytes(1) + + # Update data_channel timestamp + self.data_channel_last_received = int(time.time()) + + # check if the Modem is running in low bandwidth mode + # then set the corresponding frametype and build frame + if Modem.low_bandwidth_mode: + frametype = bytes([FR_TYPE.ARQ_DC_OPEN_N.value]) + self.log.debug("[Modem] Requesting low bandwidth mode") + else: + frametype = bytes([FR_TYPE.ARQ_DC_OPEN_W.value]) + self.log.debug("[Modem] Requesting high bandwidth mode") + + connection_frame = bytearray(self.length_sig0_frame) + connection_frame[:1] = frametype + connection_frame[1:4] = Station.dxcallsign_crc + connection_frame[4:7] = Station.mycallsign_crc + connection_frame[7:13] = helpers.callsign_to_bytes(mycallsign) + connection_frame[13:14] = self.session_id + + for attempt in range(self.data_channel_max_retries): + + self.send_data_to_socket_queue( + freedata="modem-message", + arq="transmission", + status="opening", + mycallsign=str(mycallsign, 'UTF-8'), + dxcallsign=str(self.dxcallsign, 'UTF-8'), + irs=helpers.bool_to_string(self.is_IRS) + ) + + self.log.info( + "[Modem] ARQ | DATA | TX | [" + + str(mycallsign, "UTF-8") + + "]>> <<[" + + str(self.dxcallsign, "UTF-8") + + "]", + attempt=f"{str(attempt + 1)}/{str(self.data_channel_max_retries)}", + ) + + # Let's check if we have a busy channel and if we are not in a running arq session. + if ModemParam.channel_busy and not ARQ.arq_state_event.is_set(): + self.channel_busy_handler() + + # if channel free, enqueue frame for tx + self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) + + # wait until timeout or event set + ARQ.arq_state_event.wait(timeout=self.datachannel_opening_interval) + + if ARQ.arq_state_event.is_set(): + return True + if Modem.modem_state in ["IDLE"]: + return False + + # `data_channel_max_retries` attempts have been sent. Aborting attempt & cleaning up + return False + + def arq_received_data_channel_opener(self, data_in: bytes): """ @@ -2564,6 +2544,9 @@ class DATA: # as soon as we set ARQ_STATE to DATA, transmission starts ARQ.arq_state = True + # also set the ARQ event + ARQ.arq_state_event.set() + # Update data_channel timestamp self.data_channel_last_received = int(time.time()) else: @@ -3073,6 +3056,29 @@ class DATA: + "] ", ) + + def channel_busy_handler(self): + """ + function for handling the channel busy situation + Args: + + Returns: + """ + self.log.warning("[Modem] Channel busy, waiting until free...") + self.send_data_to_socket_queue( + freedata="modem-message", + arq="transmission", + status="waiting", + mycallsign=str(self.mycallsign, 'UTF-8'), + dxcallsign=str(self.dxcallsign, 'UTF-8'), + irs=helpers.bool_to_string(self.is_IRS) + ) + + # wait while timeout not reached and our busy state is busy + channel_busy_timeout = time.time() + 5 + while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): + threading.Event().wait(0.01) + # ------------ CALCULATE TRANSFER RATES def calculate_transfer_rate_rx( self, rx_start_of_transmission: float, receivedbytes: int @@ -3263,6 +3269,7 @@ class DATA: ARQ.arq_session_state = "disconnected" ARQ.speed_list = [] ARQ.arq_state = False + ARQ.arq_state_event = threading.Event() self.arq_file_transfer = False Beacon.beacon_pause = False diff --git a/modem/static.py b/modem/static.py index 7059b8dc..e63d2956 100644 --- a/modem/static.py +++ b/modem/static.py @@ -11,6 +11,7 @@ from dataclasses import dataclass, field from typing import List import subprocess from enum import Enum +import threading # CHANNEL_STATE = 'RECEIVING_SIGNALLING' @@ -33,6 +34,7 @@ class ARQ: arq_session_state: str = "disconnected" # can be: disconnected, disconnecting, connected, connecting, failed arq_session: bool = False arq_state: bool = False + arq_state_event: threading.Event = field(default_factory=threading.Event) # ARQ PROTOCOL VERSION # v.5 - signalling frame uses datac0 # v.6 - signalling frame uses datac13 @@ -134,7 +136,7 @@ class TCIParam: @dataclass class Modem: - version = "0.11.1-alpha.1" + version = "0.11.2-alpha.1" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds From f59ccd5ae569ee397cafaa21dc4eaf0d58e2f78d Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sat, 28 Oct 2023 23:42:29 +0200 Subject: [PATCH 02/10] improved channel busy handler --- modem/data_handler.py | 130 ++++++++++++------------------------------ 1 file changed, 37 insertions(+), 93 deletions(-) diff --git a/modem/data_handler.py b/modem/data_handler.py index 43a59f42..d6000511 100644 --- a/modem/data_handler.py +++ b/modem/data_handler.py @@ -181,7 +181,7 @@ class DATA: self.data_frame_ack_timeout_seconds = 4.5 # timeout for data frame acknowledges self.rpt_ack_timeout_seconds = 4.5 # timeout for rpt frame acknowledges self.transmission_timeout = 180 # transmission timeout in seconds - self.channel_busy_timeout = 2 # time how long we want to wait until channel busy state overrides + self.channel_busy_timeout = 3 # time how long we want to wait until channel busy state overrides self.datachannel_opening_interval = self.duration_sig1_frame + self.channel_busy_timeout + 1 # time between attempts when opening data channel # Dictionary of functions and log messages used in process_data @@ -547,10 +547,9 @@ class DATA: ack_frame[3:4] = bytes([int(self.speed_level)]) ack_frame[4:8] = len(ARQ.rx_frame_buffer).to_bytes(4, byteorder="big") - # wait while timeout not reached and our busy state is busy - channel_busy_timeout = time.time() + 5 - while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): - threading.Event().wait(0.01) + # wait if we have a channel busy condition + if ModemParam.channel_busy: + self.channel_busy_handler() # Transmit frame self.enqueue_frame_for_tx([ack_frame], c2_mode=FREEDV_MODE.sig1.value) @@ -563,13 +562,12 @@ class DATA: ack_frame[1:2] = self.session_id ack_frame[2:3] = helpers.snr_to_bytes(snr) - # wait while timeout not reached and our busy state is busy - channel_busy_timeout = time.time() + 5 - while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): - threading.Event().wait(0.01) + # wait if we have a channel busy condition + if ModemParam.channel_busy: + self.channel_busy_handler() # reset burst timeout in case we had to wait too long - self.burst_last_received = time.time() + channel_busy_timeout + 8 + self.burst_last_received = time.time() + self.channel_busy_timeout + 8 # Transmit frame # TODO Do we have to send , self.send_ident_frame(False) ? # self.enqueue_frame_for_tx([ack_frame, self.send_ident_frame(False)], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) @@ -608,10 +606,9 @@ class DATA: # TODO Do we have to send ident frame? # self.enqueue_frame_for_tx([ack_frame, self.send_ident_frame(False)], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) - # wait while timeout not reached and our busy state is busy - channel_busy_timeout = time.time() + 5 - while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): - threading.Event().wait(0.01) + # wait if we have a channel busy condition + if ModemParam.channel_busy: + self.channel_busy_handler() self.enqueue_frame_for_tx([nack_frame], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) # reset burst timeout in case we had to wait too long @@ -623,7 +620,6 @@ class DATA: # increment nack counter for transmission stats self.frame_nack_counter += 1 - # we need to clear our rx burst buffer ARQ.rx_burst_buffer = [] @@ -637,11 +633,9 @@ class DATA: nack_frame[4:5] = bytes([int(tx_n_frames_per_burst)]) nack_frame[5:9] = len(ARQ.rx_frame_buffer).to_bytes(4, byteorder="big") - - # wait while timeout not reached and our busy state is busy - channel_busy_timeout = time.time() + 5 + 5 - while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): - threading.Event().wait(0.01) + # wait if we have a channel busy condition + if ModemParam.channel_busy: + self.channel_busy_handler() # TRANSMIT NACK FRAME FOR BURST self.enqueue_frame_for_tx([nack_frame], c2_mode=FREEDV_MODE.sig1.value, copies=1, repeat_delay=0) @@ -661,10 +655,9 @@ class DATA: # 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 - # wait while timeout not reached and our busy state is busy - channel_busy_timeout = time.time() + 5 - while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): - threading.Event().wait(0.01) + # wait if we have a channel busy condition + if ModemParam.channel_busy: + self.channel_busy_handler() self.enqueue_frame_for_tx([disconnection_frame], c2_mode=FREEDV_MODE.sig0.value, copies=3, repeat_delay=0) @@ -1009,9 +1002,7 @@ class DATA: mode_slots=mode_slots, ) return False - - else: - return True + return True def arq_calculate_speed_level(self, snr): current_speed_level = self.speed_level @@ -1042,6 +1033,13 @@ class DATA: # Update modes we are listening to self.set_listening_modes(False, True, self.mode_list[self.speed_level]) + self.log.debug( + "[Modem] calculated speed level", + speed_level=self.speed_level, + given_snr=ModemParam.snr, + min_snr=self.snr_list[self.speed_level], + ) + def arq_process_received_data_frame(self, data_frame, snr, signed): """ @@ -1752,35 +1750,9 @@ class DATA: state=ARQ.arq_session_state, ) - # Let's check if we have a busy channel + # wait if we have a channel busy condition if ModemParam.channel_busy: - self.log.warning("[Modem] Channel busy, waiting until free...") - self.send_data_to_socket_queue( - freedata="modem-message", - arq="session", - status="waiting", - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - ) - - # wait while timeout not reached and our busy state is busy - channel_busy_timeout = time.time() + 15 - while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): - threading.Event().wait(0.01) - - # if channel busy timeout reached stop connecting - if time.time() > channel_busy_timeout: - self.log.warning("[Modem] Channel busy, try again later...") - ARQ.arq_session_state = "failed" - self.send_data_to_socket_queue( - freedata="modem-message", - arq="session", - status="failed", - reason="busy", - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - ) - return False + self.channel_busy_handler() self.open_session() @@ -2286,8 +2258,6 @@ class DATA: # `data_channel_max_retries` attempts have been sent. Aborting attempt & cleaning up return False - - def arq_received_data_channel_opener(self, data_in: bytes): """ Received request to open data channel frame @@ -2318,7 +2288,7 @@ class DATA: # Station B already tries connecting to Station A. # For avoiding ignoring repeated connect request in case of packet loss # we are only ignoring packets in case we are ISS - if ARQ.arq_state and not self.is_IRS: + if ARQ.arq_state_event.is_set() and not self.is_IRS: return False self.is_IRS = True @@ -2336,9 +2306,6 @@ class DATA: irs=helpers.bool_to_string(self.is_IRS) ) - # n_frames_per_burst is currently unused - # n_frames_per_burst = int.from_bytes(bytes(data_in[13:14]), "big") - frametype = int.from_bytes(bytes(data_in[:1]), "big") # check if we received low bandwidth mode # possible channel constellations @@ -2386,29 +2353,14 @@ class DATA: # initially set speed_level 0 in case of bad SNR and no matching mode self.speed_level = 0 - # TODO MOVE THIS TO arq_calculate_speed_level() - # calculate speed level in correlation to latest known SNR + # calculate initial speed level in correlation to latest known SNR for i in range(len(self.mode_list)): if ModemParam.snr >= self.snr_list[i]: self.speed_level = i - # calculate if speed level fits to busy condition - mode_name = codec2.FREEDV_MODE(self.mode_list[self.speed_level]).name - mode_slots = codec2.FREEDV_MODE_USED_SLOTS[mode_name].value - if mode_slots in [ModemParam.channel_busy_slot]: + # check if speed level fits to busy condition + if not self.check_if_mode_fits_to_busy_slot(): self.speed_level = 0 - self.log.warning( - "[Modem] busy slot detection", - slots=ModemParam.channel_busy_slot, - mode_slots=mode_slots, - ) - - self.log.debug( - "[Modem] calculated speed level", - speed_level=self.speed_level, - given_snr=ModemParam.snr, - min_snr=self.snr_list[self.speed_level], - ) # Update modes we are listening to self.set_listening_modes(True, True, self.mode_list[self.speed_level]) @@ -2519,10 +2471,10 @@ class DATA: self.time_list = self.time_list_high_bw self.log.debug("[Modem] high bandwidth mode", modes=self.mode_list) - # set speed level from session opener frame which is selected by SNR measurement + # set speed level from session opener frame delegation self.speed_level = int.from_bytes(bytes(data_in[8:9]), "big") self.log.debug("[Modem] speed level selected for given SNR", speed_level=self.speed_level) - # self.speed_level = len(self.mode_list) - 1 + Station.dxgrid = b'------' helpers.add_to_heard_stations( Station.dxcallsign, @@ -2542,16 +2494,15 @@ class DATA: snr=ModemParam.snr, ) - # as soon as we set ARQ_STATE to DATA, transmission starts + # as soon as we set ARQ_STATE to True, transmission starts ARQ.arq_state = True # also set the ARQ event ARQ.arq_state_event.set() # Update data_channel timestamp self.data_channel_last_received = int(time.time()) + else: - Modem.modem_state = "IDLE" - ARQ.arq_state = False self.send_data_to_socket_queue( freedata="modem-message", arq="transmission", @@ -2561,7 +2512,6 @@ class DATA: dxcallsign=str(self.dxcallsign, 'UTF-8'), irs=helpers.bool_to_string(self.is_IRS) ) - # TODO We should display a message to this effect on the UI. self.log.warning( "[Modem] protocol version mismatch:", received=protocol_version, @@ -2744,8 +2694,6 @@ class DATA: """ self.log.warning("[Modem] Stopping transmission!") - Modem.modem_state = "IDLE" - ARQ.arq_state = False self.send_data_to_socket_queue( freedata="modem-message", arq="transmission", @@ -2761,7 +2709,6 @@ class DATA: # TODO Not sure if we really need the session id when disconnecting # stop_frame[1:2] = self.session_id stop_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign) - self.enqueue_frame_for_tx([stop_frame], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) self.arq_cleanup() @@ -3067,15 +3014,12 @@ class DATA: self.log.warning("[Modem] Channel busy, waiting until free...") self.send_data_to_socket_queue( freedata="modem-message", - arq="transmission", + channel="busy", status="waiting", - mycallsign=str(self.mycallsign, 'UTF-8'), - dxcallsign=str(self.dxcallsign, 'UTF-8'), - irs=helpers.bool_to_string(self.is_IRS) ) # wait while timeout not reached and our busy state is busy - channel_busy_timeout = time.time() + 5 + channel_busy_timeout = time.time() + self.channel_busy_timeout while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): threading.Event().wait(0.01) From 4a47c3f58c0de25ef22150db6abb696ea6ce1033 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Sun, 29 Oct 2023 23:10:12 +0100 Subject: [PATCH 03/10] adjusted morse module --- modem/cw.py | 42 +++++++++++++++++++++--------------------- modem/data_handler.py | 4 +--- modem/modem.py | 17 ++++------------- 3 files changed, 26 insertions(+), 37 deletions(-) diff --git a/modem/cw.py b/modem/cw.py index 6a678050..993e5513 100644 --- a/modem/cw.py +++ b/modem/cw.py @@ -6,8 +6,10 @@ import numpy as np """ + + class MorseCodePlayer: - def __init__(self, wpm=150, f=1500, fs=48000): + def __init__(self, wpm=25, f=1500, fs=48000): self.wpm = wpm self.f0 = f self.fs = fs @@ -39,28 +41,26 @@ class MorseCodePlayer: signal = np.array([], dtype=np.int16) for char in morse: if char == '.': - duration = int(self.dot_duration * self.fs) - s = np.sin(2 * np.pi * self.f0 * np.arange(duration) / self.fs) - signal = np.concatenate((signal, s * 32767)) - pause_duration = int(self.pause_duration * self.fs) - signal = np.concatenate((signal, np.zeros(pause_duration, dtype=np.int16))) + duration = self.dot_duration # Using class-defined duration + t = np.linspace(0, duration, int(self.fs * duration), endpoint=False) + s = 0.5 * np.sin(2 * np.pi * self.f0 * t) + signal = np.concatenate((signal, np.int16(s * 32767))) + pause_samples = int(self.pause_duration * self.fs) + signal = np.concatenate((signal, np.zeros(pause_samples, dtype=np.int16))) + elif char == '-': - duration = int(self.dash_duration * self.fs) - s = np.sin(2 * np.pi * self.f0 * np.arange(duration) / self.fs) - signal = np.concatenate((signal, s * 32767)) - pause_duration = int(self.pause_duration * self.fs) - signal = np.concatenate((signal, np.zeros(pause_duration, dtype=np.int16))) + duration = self.dash_duration # Using class-defined duration + t = np.linspace(0, duration, int(self.fs * duration), endpoint=False) + s = 0.5 * np.sin(2 * np.pi * self.f0 * t) + signal = np.concatenate((signal, np.int16(s * 32767))) + pause_samples = int(self.pause_duration * self.fs) + signal = np.concatenate((signal, np.zeros(pause_samples, dtype=np.int16))) + elif char == ' ': - pause_duration = int(self.word_pause_duration * self.fs) - signal = np.concatenate((signal, np.zeros(pause_duration, dtype=np.int16))) - pause_duration = int(self.pause_duration * self.fs) - signal = np.concatenate((signal, np.zeros(pause_duration, dtype=np.int16))) - - pause_duration = int(self.word_pause_duration * self.fs) - #signal = np.concatenate((signal, np.zeros(pause_duration, dtype=np.int16))) - - # Convert the signal to mono (single-channel) - #signal = signal.reshape(-1, 1) + pause_samples = int(self.word_pause_duration * self.fs) + signal = np.concatenate((signal, np.zeros(pause_samples, dtype=np.int16))) + pause_samples = int(self.pause_duration * self.fs) + signal = np.concatenate((signal, np.zeros(pause_samples, dtype=np.int16))) return signal diff --git a/modem/data_handler.py b/modem/data_handler.py index d6000511..ed65412c 100644 --- a/modem/data_handler.py +++ b/modem/data_handler.py @@ -2856,9 +2856,7 @@ class DATA: 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.sig0.value, copies=1, repeat_delay=0) - # FIXME Remove or change this in later versions for full CW support - # Modem.transmitting = True - # modem.MODEM_TRANSMIT_QUEUE.put(["morse", 1, 0, "123"]) + #modem.MODEM_TRANSMIT_QUEUE.put(["morse", 1, 0, self.mycallsign]) def received_cq(self, data_in: bytes) -> None: """ diff --git a/modem/modem.py b/modem/modem.py index 6df36fc5..3953052c 100644 --- a/modem/modem.py +++ b/modem/modem.py @@ -29,7 +29,7 @@ import structlog import ujson as json import tci # FIXME used for def transmit_morse -# import cw +import cw from queues import DATA_QUEUE_RECEIVED, MODEM_RECEIVED_QUEUE, MODEM_TRANSMIT_QUEUE, RIGCTLD_COMMAND_QUEUE, \ AUDIO_RECEIVED_QUEUE, AUDIO_TRANSMIT_QUEUE, MESH_RECEIVED_QUEUE @@ -745,18 +745,7 @@ class RF: ) start_of_transmission = time.time() - txbuffer = cw.MorseCodePlayer().text_to_signal("DJ2LS-1") - print(txbuffer) - print(type(txbuffer)) - x = np.frombuffer(txbuffer, dtype=np.int16) - print(type(x)) - txbuffer_out = x - print(txbuffer_out) - - #if not HamlibParam.hamlib_radiocontrol in ["tci"]: - # txbuffer_out = self.resampler.resample8_to_48(x) - #else: - # txbuffer_out = x + txbuffer_out = cw.MorseCodePlayer().text_to_signal("DJ2LS-1") self.mod_out_locked = True self.enqueue_modulation(txbuffer_out) @@ -808,6 +797,8 @@ class RF: self.log.debug("[MDM] ON AIR TIME", time=transmission_time) def enqueue_modulation(self, txbuffer_out): + + chunk_length = self.AUDIO_FRAMES_PER_BUFFER_TX # 4800 chunk = [ txbuffer_out[i: i + chunk_length] From 5d111cac5b50a482375e76f915d4a5d79b48ab64 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Tue, 31 Oct 2023 09:53:31 +0100 Subject: [PATCH 04/10] enable / disable morse identifier --- modem/config.py | 7 ++++--- modem/daemon.py | 6 ++++++ modem/data_handler.py | 7 ++++++- modem/main.py | 13 ++++++++++++- modem/static.py | 1 + 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/modem/config.py b/modem/config.py index 032e7283..a977e3f5 100644 --- a/modem/config.py +++ b/modem/config.py @@ -77,14 +77,15 @@ class CONFIG: 'explorer': data[17], 'stats': data[19], 'fsk': data[13], - 'tx_delay': data[21] - } + 'tx_delay': data[21], + 'transmit_morse_identifier' : data[25] + } self.config['TCI'] = {'#TCI settings': None, 'ip': data[22], 'port': data[23] } - self.config['MESH'] = {'#TCI settings': None, + self.config['MESH'] = {'#Mesh settings': None, 'enable_protocol': data[24] } diff --git a/modem/daemon.py b/modem/daemon.py index dca02653..2c993a32 100755 --- a/modem/daemon.py +++ b/modem/daemon.py @@ -496,6 +496,12 @@ class DAEMON: if data[24] == "True": options.append("--mesh") + #Morse identifier + print(data[25]) + if data[25] == "True": + options.append("--morse") + + # safe data to config file config.write_entire_config(data) diff --git a/modem/data_handler.py b/modem/data_handler.py index ed65412c..63f7a72a 100644 --- a/modem/data_handler.py +++ b/modem/data_handler.py @@ -1966,6 +1966,10 @@ class DATA: # we need to send disconnect frame before doing arq cleanup # we would lose our session id then self.send_disconnect_frame() + + # transmit morse identifier if configured + if Modem.transmit_morse_identifier: + modem.MODEM_TRANSMIT_QUEUE.put(["morse", 1, 0, self.mycallsign]) self.arq_cleanup() def received_session_close(self, data_in: bytes): @@ -2856,7 +2860,8 @@ class DATA: 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.sig0.value, copies=1, repeat_delay=0) - #modem.MODEM_TRANSMIT_QUEUE.put(["morse", 1, 0, self.mycallsign]) + if Modem.transmit_morse_identifier: + modem.MODEM_TRANSMIT_QUEUE.put(["morse", 1, 0, self.mycallsign]) def received_cq(self, data_in: bytes) -> None: """ diff --git a/modem/main.py b/modem/main.py index b96be8e1..0a1cc7a9 100755 --- a/modem/main.py +++ b/modem/main.py @@ -261,6 +261,15 @@ if __name__ == "__main__": help="Enable and set hmac message salt", ) + PARSER.add_argument( + "--morse", + dest="transmit_morse_identifier", + action="store_true", + default=True, + help="Enable and send a morse identifier on disconnect an beacon", + ) + + ARGS = PARSER.parse_args() # set save to folder state for allowing downloading files to local file system @@ -316,7 +325,7 @@ if __name__ == "__main__": ModemParam.tx_delay = ARGS.tx_delay MeshParam.enable_protocol = ARGS.enable_mesh Modem.enable_hmac = ARGS.enable_hmac - + Modem.transmit_morse_identifier = ARGS.transmit_morse_identifier except Exception as e: log.error("[DMN] Error reading config file", exception=e) @@ -368,6 +377,8 @@ if __name__ == "__main__": TCIParam.port = int(conf.get('TCI', 'tci_port', '50001')) ModemParam.tx_delay = int(conf.get('Modem', 'tx_delay', '0')) MeshParam.enable_protocol = conf.get('MESH','mesh_enable','False') + MeshParam.transmit_morse_identifier = conf.get('Modem','transmit_morse_identifier','False') + except KeyError as e: log.warning("[CFG] Error reading config file near", key=str(e)) except Exception as e: diff --git a/modem/static.py b/modem/static.py index e63d2956..3ccca594 100644 --- a/modem/static.py +++ b/modem/static.py @@ -151,6 +151,7 @@ class Modem: heard_stations = [] listen: bool = True enable_hmac: bool = True + transmit_morse_identifier: bool = False # ------------ From 3f49b639309e9e4bc5932a935450bbb9239eb651 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Tue, 31 Oct 2023 14:00:03 +0100 Subject: [PATCH 05/10] adjusted arq timeouts... --- modem/data_handler.py | 70 ++++++++++++++++++++++++++----------------- modem/main.py | 2 +- modem/modem.py | 18 +++++++++-- modem/static.py | 2 +- 4 files changed, 60 insertions(+), 32 deletions(-) diff --git a/modem/data_handler.py b/modem/data_handler.py index 63f7a72a..084283a2 100644 --- a/modem/data_handler.py +++ b/modem/data_handler.py @@ -56,10 +56,16 @@ class DATA: self.length_sig0_frame = 14 self.length_sig1_frame = 14 - # duration of signalling frame - self.duration_sig0_frame = 2.3 - self.duration_sig1_frame = 2.3 - self.longest_duration = 5.8 # datac5 + # duration of frames + self.duration_datac4 = 5.17 + self.duration_datac13 = 2.0 + self.duration_datac1 = 4.18 + self.duration_datac3 = 3.19 + self.duration_sig0_frame = self.duration_datac13 + self.duration_sig1_frame = self.duration_datac13 + self.longest_duration = self.duration_datac4 + + # hold session id self.session_id = bytes(1) @@ -123,7 +129,7 @@ class DATA: # List for minimum SNR operating level for the corresponding mode in self.mode_list self.snr_list_low_bw = [-100] # List for time to wait for corresponding mode in seconds - self.time_list_low_bw = [6 + self.duration_sig0_frame + 1] + self.time_list_low_bw = [self.duration_datac4] # --------------------- HIGH BANDWIDTH @@ -139,7 +145,7 @@ class DATA: # test with 6,7 --> caused sometimes a frame timeout if ack frame takes longer # TODO Need to check why ACK frames needs more time # TODO Adjust these times - self.time_list_high_bw = [6 + self.duration_sig0_frame + 1, 6 + self.duration_sig0_frame + 1, 6 + self.duration_sig0_frame + 1] + self.time_list_high_bw = [self.duration_datac4, self.duration_datac3, self.duration_datac1] # -------------- AVAILABLE MODES END----------- # Mode list for selecting between low bandwidth ( 500Hz ) and modes with higher bandwidth @@ -566,11 +572,7 @@ class DATA: if ModemParam.channel_busy: self.channel_busy_handler() - # reset burst timeout in case we had to wait too long - self.burst_last_received = time.time() + self.channel_busy_timeout + 8 # Transmit frame - # TODO Do we have to send , self.send_ident_frame(False) ? - # self.enqueue_frame_for_tx([ack_frame, self.send_ident_frame(False)], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) self.enqueue_frame_for_tx([ack_frame], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) def send_retransmit_request_frame(self) -> None: @@ -614,7 +616,7 @@ class DATA: # reset burst timeout in case we had to wait too long self.burst_last_received = time.time() - def send_burst_nack_frame_watchdog(self, snr: bytes, tx_n_frames_per_burst) -> None: + def send_burst_nack_frame_watchdog(self, tx_n_frames_per_burst) -> None: """Build and send NACK frame for watchdog timeout""" # increment nack counter for transmission stats @@ -628,7 +630,7 @@ class DATA: nack_frame = bytearray(self.length_sig1_frame) nack_frame[:1] = bytes([FR_TYPE.BURST_NACK.value]) nack_frame[1:2] = self.session_id - nack_frame[2:3] = helpers.snr_to_bytes(snr) + nack_frame[2:3] = helpers.snr_to_bytes(0) nack_frame[3:4] = bytes([int(self.speed_level)]) nack_frame[4:5] = bytes([int(tx_n_frames_per_burst)]) nack_frame[5:9] = len(ARQ.rx_frame_buffer).to_bytes(4, byteorder="big") @@ -649,11 +651,6 @@ class DATA: disconnection_frame[:1] = bytes([FR_TYPE.ARQ_SESSION_CLOSE.value]) disconnection_frame[1:2] = self.session_id disconnection_frame[2:5] = Station.dxcallsign_crc - # TODO Needed? disconnection_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign) - # self.enqueue_frame_for_tx([disconnection_frame, self.send_ident_frame(False)], c2_mode=FREEDV_MODE.sig0.value, copies=5, repeat_delay=0) - # TODO We need to add the ident frame feature with a seperate PR after publishing latest protocol - # 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 # wait if we have a channel busy condition if ModemParam.channel_busy: @@ -809,8 +806,12 @@ class DATA: ): self.arq_calculate_speed_level(snr) - self.data_channel_last_received = int(time.time()) + 6 + 6 - self.burst_last_received = int(time.time()) + 6 + 6 + # TIMING TEST + #self.data_channel_last_received = int(time.time()) + 6 + 6 + #self.burst_last_received = int(time.time()) + 6 + 6 + self.data_channel_last_received = int(time.time()) + self.burst_last_received = int(time.time()) + # Create and send ACK frame self.log.info("[Modem] ARQ | RX | SENDING ACK", finished=ARQ.arq_seconds_until_finish, bytesperminute=ARQ.bytes_per_minute) @@ -2393,6 +2394,7 @@ class DATA: ) # Reset data_channel/burst timestamps + # TIMING TEST self.data_channel_last_received = int(time.time()) self.burst_last_received = int(time.time() + 10) # we might need some more time so lets increase this @@ -2441,7 +2443,9 @@ class DATA: # set start of transmission for our statistics self.rx_start_of_transmission = time.time() + # TIMING TEST # Reset data_channel/burst timestamps once again for avoiding running into timeout + # and therefore sending a NACK self.data_channel_last_received = int(time.time()) self.burst_last_received = int(time.time() + 10) # we might need some more time so lets increase this @@ -2783,6 +2787,8 @@ class DATA: else: self.enqueue_frame_for_tx([beacon_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) + if Modem.transmit_morse_identifier: + modem.MODEM_TRANSMIT_QUEUE.put(["morse", 1, 0, self.mycallsign]) self.beacon_interval_timer = time.time() + self.beacon_interval while ( @@ -2860,8 +2866,7 @@ class DATA: 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.sig0.value, copies=1, repeat_delay=0) - if Modem.transmit_morse_identifier: - modem.MODEM_TRANSMIT_QUEUE.put(["morse", 1, 0, self.mycallsign]) + def received_cq(self, data_in: bytes) -> None: """ @@ -3329,10 +3334,18 @@ class DATA: if frames_left == 0: frames_left = 1 - timeout = self.burst_last_received + (self.time_list[self.speed_level] * frames_left) + # timeout is reached, if we didnt receive data, while we waited + # for the corresponding data frame + the transmitted signalling frame of ack/nack + # + a small offset of about 1 second + timeout = self.burst_last_received + (self.time_list[self.speed_level] * frames_left) + self.duration_sig0_frame + self.channel_busy_timeout + 1 + # TODO Enable this for development - # print(f"timeout expected in:{round(timeout - time.time())} | frames left: {frames_left} of {self.rx_n_frames_per_burst} | speed level: {self.speed_level}") - if timeout <= time.time() or modem_error_state: + print(f"timeout expected in:{round(timeout - time.time())} | frames left: {frames_left} of {self.rx_n_frames_per_burst} | speed level: {self.speed_level}") + # if timeout is expired, but we are receiving codec2 data, + # better wait some more time because data might be important for us + # reason for this situation can be delays on IRS and ISS, maybe because both had a busy channel condition. + # Nevertheless, we need to keep timeouts short for efficiency + if timeout <= time.time() or modem_error_state and not ModemParam.is_codec2_traffic and not Modem.transmitting: self.log.warning( "[Modem] Burst decoding error or timeout", attempt=self.n_retries_per_burst, @@ -3343,7 +3356,7 @@ class DATA: print( f"frames_per_burst {self.rx_n_frame_of_burst} / {self.rx_n_frames_per_burst}, Repeats: {self.burst_rpt_counter} Nones: {ARQ.rx_burst_buffer.count(None)}") - + # check if we have N frames per burst > 1 if self.rx_n_frames_per_burst > 1 and self.burst_rpt_counter < 3 and ARQ.rx_burst_buffer.count(None) > 0: # reset self.burst_last_received self.burst_last_received = time.time() + self.time_list[self.speed_level] * frames_left @@ -3352,8 +3365,8 @@ class DATA: else: - # reset self.burst_last_received - self.burst_last_received = time.time() + self.time_list[self.speed_level] + # reset self.burst_last_received counter + self.burst_last_received = time.time() # reduce speed level if nack counter increased self.frame_received_counter = 0 @@ -3374,13 +3387,14 @@ class DATA: self.set_listening_modes(True, True, self.mode_list[self.speed_level]) # TODO Does SNR make sense for NACK if we dont have an actual SNR information? - self.send_burst_nack_frame_watchdog(0, tx_n_frames_per_burst) + self.send_burst_nack_frame_watchdog(tx_n_frames_per_burst) # Update data_channel timestamp # TODO Disabled this one for testing. # self.data_channel_last_received = time.time() self.n_retries_per_burst += 1 else: + # debugging output # print((self.data_channel_last_received + self.time_list[self.speed_level])-time.time()) pass diff --git a/modem/main.py b/modem/main.py index 0a1cc7a9..1e8091d9 100755 --- a/modem/main.py +++ b/modem/main.py @@ -265,7 +265,7 @@ if __name__ == "__main__": "--morse", dest="transmit_morse_identifier", action="store_true", - default=True, + default=False, help="Enable and send a morse identifier on disconnect an beacon", ) diff --git a/modem/modem.py b/modem/modem.py index 3953052c..51277e56 100644 --- a/modem/modem.py +++ b/modem/modem.py @@ -80,6 +80,8 @@ class RF: self.AUDIO_CHANNELS = 1 self.MODE = 0 + self.is_codec2_traffic_cooldown = 20 + self.is_codec2_traffic_counter = 0 # Locking state for mod out so buffer will be filled before we can use it # https://github.com/DJ2LS/FreeDATA/issues/127 # https://github.com/DJ2LS/FreeDATA/issues/99 @@ -845,6 +847,8 @@ class RF: :return: NIN from freedv instance :rtype: int """ + + nbytes = 0 try: while self.stream.active: @@ -862,10 +866,11 @@ class RF: # 10 error decoding == NACK rx_status = codec2.api.freedv_get_rx_status(freedv) - if rx_status != 0: + if rx_status not in [0]: # we need to disable this if in testmode as its causing problems with FIFO it seems if not TESTMODE: ModemParam.is_codec2_traffic = True + self.is_codec2_traffic_counter = self.is_codec2_traffic_cooldown if not ModemParam.channel_busy: self.log.debug("[MDM] Setting channel_busy since codec2 data detected") ModemParam.channel_busy=True @@ -877,6 +882,15 @@ class RF: else: ModemParam.is_codec2_traffic = False + # decrement codec traffic counter for making state smoother + + print(f"{mode_name}: {self.is_codec2_traffic_counter}") + if self.is_codec2_traffic_counter > 0: + self.is_codec2_traffic_counter -= 1 + ModemParam.is_codec2_traffic = True + else: + ModemParam.is_codec2_traffic = False + if rx_status == 10: state_buffer.append(rx_status) @@ -1357,7 +1371,7 @@ class RF: ModemParam.channel_busy_slot[slot] = False # increment slot slot += 1 - if (addDelay): + if addDelay: # Limit delay counter to a maximum of 200. The higher this value, # the longer we will wait until releasing state ModemParam.channel_busy = True diff --git a/modem/static.py b/modem/static.py index 3ccca594..83d8149d 100644 --- a/modem/static.py +++ b/modem/static.py @@ -136,7 +136,7 @@ class TCIParam: @dataclass class Modem: - version = "0.11.2-alpha.1" + version = "0.11.2-alpha.2" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds From 5632310b8c9728a7c867b11526d73c008833e245 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Tue, 31 Oct 2023 15:29:33 +0100 Subject: [PATCH 06/10] added rx audio level and moved from linear to dB --- gui/src/components/main_modals.vue | 30 ++++++++++++--- gui/src/js/daemon.ts | 1 + gui/src/js/settingsHandler.ts | 3 +- gui/src/js/sock.js | 18 +++++++++ gui/src/store/settingsStore.js | 3 ++ gui/src/store/stateStore.js | 3 ++ modem/config.py | 1 + modem/daemon.py | 2 + modem/main.py | 13 ++++++- modem/modem.py | 62 +++++++++++++++--------------- modem/sock.py | 18 +++++++++ modem/static.py | 3 +- 12 files changed, 118 insertions(+), 39 deletions(-) diff --git a/gui/src/components/main_modals.vue b/gui/src/components/main_modals.vue index 590ba5a7..bbe36c2b 100644 --- a/gui/src/components/main_modals.vue +++ b/gui/src/components/main_modals.vue @@ -17,17 +17,22 @@ import { getNewMessagesByDXCallsign, } from "../js/chatHandler"; -import { sendTestFrame, setTxAudioLevel } from "../js/sock.js"; +import { sendTestFrame, setTxAudioLevel, setRxAudioLevel } from "../js/sock.js"; function tuneAudio() { sendTestFrame(); } -function set_audio_level() { +function set_tx_audio_level() { saveSettingsToFile(); setTxAudioLevel(settings.tx_audio_level); } +function set_rx_audio_level() { + saveSettingsToFile(); + setRxAudioLevel(settings.rx_audio_level); +} + function deleteChat() { //console.log(chat.selectedCallsign) deleteChatByCallsign(chat.selectedCallsign); @@ -1194,6 +1199,21 @@ const transmissionSpeedChartDataMessageInfo = computed(() => ({ Transmit +
+ RX Level + {{ settings.rx_audio_level }} + + +
TX Level {{ settings.tx_audio_level }} @@ -1201,11 +1221,11 @@ const transmissionSpeedChartDataMessageInfo = computed(() => ({
diff --git a/gui/src/js/daemon.ts b/gui/src/js/daemon.ts index f7703e6b..fdfab000 100644 --- a/gui/src/js/daemon.ts +++ b/gui/src/js/daemon.ts @@ -184,6 +184,7 @@ export function startModem() { tuning_range_fmin: settings.tuning_range_fmin, tuning_range_fmax: settings.tuning_range_fmax, tx_audio_level: settings.tx_audio_level, + rx_audio_level: settings.rx_audio_level, respond_to_cq: settings.respond_to_cq, rx_buffer_size: settings.rx_buffer_size, enable_explorer: settings.enable_explorer, diff --git a/gui/src/js/settingsHandler.ts b/gui/src/js/settingsHandler.ts index d18464f7..6e744078 100644 --- a/gui/src/js/settingsHandler.ts +++ b/gui/src/js/settingsHandler.ts @@ -55,7 +55,8 @@ const configDefaultSettings = "daemon_port": 3001,\ "rx_audio" : "",\ "tx_audio" : "",\ - "tx_audio_level" : 100,\ + "tx_audio_level" : 0,\ + "rx_audio_level" : 0,\ "mycall": "AA0AA-0",\ "myssid": "0",\ "mygrid": "JN40aa",\ diff --git a/gui/src/js/sock.js b/gui/src/js/sock.js index 72282466..643d886a 100644 --- a/gui/src/js/sock.js +++ b/gui/src/js/sock.js @@ -31,6 +31,8 @@ const split_char = "0;1;"; // global to keep track of Modem connection error emissions var modemShowConnectStateError = 1; var setTxAudioLevelOnce = true; +var setRxAudioLevelOnce = true; + // network connection Timeout setTimeout(connectModem, 2000); @@ -172,6 +174,7 @@ client.on("data", function (socketdata) { stateStore.mode = data["mode"]; stateStore.bandwidth = data["bandwidth"]; stateStore.tx_audio_level = data["audio_level"]; + stateStore.rx_audio_level = data["audio_level"]; // if audio level is different from config one, send new audio level to modem //console.log(parseInt(stateStore.tx_audio_level)) //console.log(parseInt(settings.tx_audio_level)) @@ -185,6 +188,16 @@ client.on("data", function (socketdata) { setTxAudioLevel(settings.tx_audio_level); } + if ( + parseInt(stateStore.rx_audio_level) !== + parseInt(settings.rx_audio_level) && + setRxAudioLevelOnce === true + ) { + setRxAudioLevelOnce = false; + console.log(setRxAudioLevelOnce); + setRxAudioLevel(settings.rx_audio_level); + } + stateStore.dbfs_level = data["audio_dbfs"]; stateStore.ptt_state = data["ptt_state"]; stateStore.speed_level = data["speed_level"]; @@ -541,6 +554,11 @@ export function setTxAudioLevel(value) { '{"type" : "set", "command" : "tx_audio_level", "value" : "' + value + '"}'; writeTncCommand(command); } +export function setRxAudioLevel(value) { + var command = + '{"type" : "set", "command" : "rx_audio_level", "value" : "' + value + '"}'; + writeTncCommand(command); +} // Send Message export function sendMessage(obj) { diff --git a/gui/src/store/settingsStore.js b/gui/src/store/settingsStore.js index 3dc7c0a8..d28ce501 100644 --- a/gui/src/store/settingsStore.js +++ b/gui/src/store/settingsStore.js @@ -6,6 +6,7 @@ export const useSettingsStore = defineStore("settingsStore", () => { var tx_audio = ref(); var rx_audio = ref(); var tx_audio_level = ref(); + var rx_audio_level = ref(); // network var modem_host = ref("127.0.0.1"); @@ -159,6 +160,7 @@ export const useSettingsStore = defineStore("settingsStore", () => { tx_audio: tx_audio.value, rx_audio: rx_audio.value, tx_audio_level: tx_audio_level.value, + rx_audio_level: rx_audio_level.value, }; return config_export; @@ -227,5 +229,6 @@ export const useSettingsStore = defineStore("settingsStore", () => { getSerialDevices, serial_devices, tx_audio_level, + rx_audio_level, }; }); diff --git a/gui/src/store/stateStore.js b/gui/src/store/stateStore.js index 235b5c68..46f56b8c 100644 --- a/gui/src/store/stateStore.js +++ b/gui/src/store/stateStore.js @@ -41,6 +41,8 @@ export const useStateStore = defineStore("stateStore", () => { var hamlib_status = ref(""); var tx_audio_level = ref(""); + var rx_audio_level = ref(""); + var alc = ref(""); var is_codec2_traffic = ref(""); @@ -159,6 +161,7 @@ export const useStateStore = defineStore("stateStore", () => { audio_recording, hamlib_status, tx_audio_level, + rx_audio_level, alc, updateTncState, arq_transmission_percent, diff --git a/modem/config.py b/modem/config.py index a977e3f5..6d746013 100644 --- a/modem/config.py +++ b/modem/config.py @@ -58,6 +58,7 @@ class CONFIG: 'rx': data[3], 'tx': data[4], 'txaudiolevel': data[14], + 'rxaudiolevel': data[26], 'auto_tune': data[19] } diff --git a/modem/daemon.py b/modem/daemon.py index 2c993a32..c6aec992 100755 --- a/modem/daemon.py +++ b/modem/daemon.py @@ -501,6 +501,8 @@ class DAEMON: if data[25] == "True": options.append("--morse") + options.append("--rx-audio-level") + options.append(data[26]) # safe data to config file config.write_entire_config(data) diff --git a/modem/main.py b/modem/main.py index 1e8091d9..fc35e04c 100755 --- a/modem/main.py +++ b/modem/main.py @@ -190,10 +190,17 @@ if __name__ == "__main__": PARSER.add_argument( "--tx-audio-level", dest="tx_audio_level", - default=50, + default=0, help="Set the tx audio level at an early stage", type=int, ) + PARSER.add_argument( + "--rx-audio-level", + dest="rx_audio_level", + default=0, + help="Set the rx audio level at an early stage", + type=int, + ) PARSER.add_argument( "--rx-buffer-size", dest="rx_buffer_size", @@ -315,6 +322,7 @@ if __name__ == "__main__": ModemParam.tuning_range_fmin = ARGS.tuning_range_fmin ModemParam.tuning_range_fmax = ARGS.tuning_range_fmax AudioParam.tx_audio_level = ARGS.tx_audio_level + AudioParam.rx_audio_level = ARGS.rx_audio_level Modem.respond_to_cq = ARGS.enable_respond_to_cq ARQ.rx_buffer_size = ARGS.rx_buffer_size Modem.enable_explorer = ARGS.enable_explorer @@ -367,7 +375,8 @@ if __name__ == "__main__": Modem.low_bandwidth_mode = conf.get('Modem', 'narrowband', 'False') ModemParam.tuning_range_fmin = float(conf.get('Modem', 'fmin', '-50.0')) ModemParam.tuning_range_fmax = float(conf.get('Modem', 'fmax', '50.0')) - AudioParam.tx_audio_level = int(conf.get('AUDIO', 'txaudiolevel', '100')) + AudioParam.tx_audio_level = int(conf.get('AUDIO', 'txaudiolevel', '0')) + AudioParam.rx_audio_level = int(conf.get('AUDIO', 'rxaudiolevel', '0')) Modem.respond_to_cq = conf.get('Modem', 'qrv', 'True') ARQ.rx_buffer_size = int(conf.get('Modem', 'rx_buffer_size', '16')) Modem.enable_explorer = conf.get('Modem', 'explorer', 'False') diff --git a/modem/modem.py b/modem/modem.py index 51277e56..9bee695e 100644 --- a/modem/modem.py +++ b/modem/modem.py @@ -16,19 +16,16 @@ import sys import threading import time from collections import deque -import wave import codec2 import itertools import numpy as np import sock import sounddevice as sd -import static from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem from static import FRAME_TYPE import structlog import ujson as json import tci -# FIXME used for def transmit_morse import cw from queues import DATA_QUEUE_RECEIVED, MODEM_RECEIVED_QUEUE, MODEM_TRANSMIT_QUEUE, RIGCTLD_COMMAND_QUEUE, \ AUDIO_RECEIVED_QUEUE, AUDIO_TRANSMIT_QUEUE, MESH_RECEIVED_QUEUE @@ -462,6 +459,7 @@ class RF: # self.log.debug("[MDM] callback") x = np.frombuffer(data_in48k, dtype=np.int16) x = self.resampler.resample48_to_8(x) + x = set_audio_volume(x, AudioParam.rx_audio_level) # audio recording for debugging purposes if AudioParam.audio_record: @@ -815,7 +813,6 @@ class RF: # self.log.debug("[MDM] mod out shorter than audio buffer", delta=delta) self.modoutqueue.append(c) - def demodulate_audio( self, audiobuffer: codec2.audio_buffer, @@ -848,7 +845,6 @@ class RF: :rtype: int """ - nbytes = 0 try: while self.stream.active: @@ -874,7 +870,7 @@ class RF: if not ModemParam.channel_busy: self.log.debug("[MDM] Setting channel_busy since codec2 data detected") ModemParam.channel_busy=True - ModemParam.channel_busy_delay+=10 + ModemParam.channel_busy_delay += 10 self.log.debug( "[MDM] [demod_audio] modem state", mode=mode_name, rx_status=rx_status, sync_flag=codec2.api.rx_sync_flags_to_text[rx_status] @@ -883,8 +879,6 @@ class RF: ModemParam.is_codec2_traffic = False # decrement codec traffic counter for making state smoother - - print(f"{mode_name}: {self.is_codec2_traffic_counter}") if self.is_codec2_traffic_counter > 0: self.is_codec2_traffic_counter -= 1 ModemParam.is_codec2_traffic = True @@ -902,7 +896,6 @@ class RF: # process commands only if Modem.listen = True if Modem.listen: - # ignore data channel opener frames for avoiding toggle states # use case: opener already received, but ack got lost and we are receiving # an opener again @@ -1144,7 +1137,7 @@ class RF: def get_scatter(self, freedv: ctypes.c_void_p) -> None: """ Ask codec2 for data about the received signal and calculate the scatter plot. - Side-effect: sets ModemParam.scatter + Side effect: sets ModemParam.scatter :param freedv: codec2 instance to query :type freedv: ctypes.c_void_p @@ -1186,7 +1179,7 @@ class RF: """ Ask codec2 for data about the received signal and calculate the signal-to-noise ratio. - Side-effect: sets ModemParam.snr + Side effect: sets ModemParam.snr :param freedv: codec2 instance to query :type freedv: ctypes.c_void_p @@ -1232,7 +1225,7 @@ class RF: def update_rig_data(self) -> None: """ Request information about the current state of the radio via hamlib - Side-effect: sets + Side effect: sets - HamlibParam.hamlib_frequency - HamlibParam.hamlib_mode - HamlibParam.hamlib_bandwidth @@ -1263,6 +1256,7 @@ class RF: e=e, ) threading.Event().wait(1) + def calculate_fft(self) -> None: """ Calculate an average signal strength of the channel to assess @@ -1465,31 +1459,39 @@ def get_bytes_per_frame(mode: int) -> int: # get number of bytes per frame for mode return int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8) - -def set_audio_volume(datalist, volume: float) -> np.int16: +def set_audio_volume(datalist: np.ndarray, dB: float) -> np.ndarray: """ - Scale values for the provided audio samples by volume, - `volume` is clipped to the range of 0-200 + Scale values for the provided audio samples by dB. :param datalist: Audio samples to scale - :type datalist: NDArray[np.int16] - :param volume: "Percentage" (0-200) to scale samples - :type volume: float + :type datalist: np.ndarray + :param dB: Decibels to scale samples, constrained to the range [-50, 50] + :type dB: float :return: Scaled audio samples - :rtype: np.int16 + :rtype: np.ndarray """ - # make sure we have float as data type to avoid crash try: - volume = float(volume) - except Exception as e: - print(f"[MDM] changing audio volume failed with error: {e}") - volume = 100.0 + dB = float(dB) + except ValueError as e: + print(f"[MDM] Changing audio volume failed with error: {e}") + dB = 0.0 # 0 dB means no change - # Clip volume provided to acceptable values - volume = np.clip(volume, 0, 200) # limit to max value of 255 - # Scale samples by the ratio of volume / 100.0 - data = np.fromstring(datalist, np.int16) * (volume / 100.0) # type: ignore - return data.astype(np.int16) + # Clip dB value to the range [-50, 50] + dB = np.clip(dB, -30, 20) + + # Ensure datalist is an np.ndarray + if not isinstance(datalist, np.ndarray): + print("[MDM] Invalid data type for datalist. Expected np.ndarray.") + return datalist + + # Convert dB to linear scale + scale_factor = 10 ** (dB / 20) + + # Scale samples + scaled_data = datalist * scale_factor + + # Clip values to int16 range and convert data type + return np.clip(scaled_data, -32768, 32767).astype(np.int16) def get_modem_error_state(): diff --git a/modem/sock.py b/modem/sock.py index a222d440..d00b4cbb 100644 --- a/modem/sock.py +++ b/modem/sock.py @@ -256,6 +256,12 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): else: self.modem_set_tx_audio_level(received_json) + # SET RX AUDIO LEVEL + if received_json["type"] == "set" and received_json["command"] == "rx_audio_level": + if TESTMODE: + ThreadedTCPRequestHandler.modem_set_rx_audio_level(None, received_json) + else: + self.modem_set_rx_audio_level(received_json) # TRANSMIT TEST FRAME if received_json["type"] == "set" and received_json["command"] == "send_test_frame": @@ -493,6 +499,18 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): command=received_json, ) + def modem_set_rx_audio_level(self, received_json): + try: + AudioParam.rx_audio_level = int(received_json["value"]) + command_response("rx_audio_level", True) + + except Exception as err: + command_response("rx_audio_level", False) + log.warning( + "[SCK] TX audio command execution error", + e=err, + command=received_json, + ) def modem_set_send_test_frame(self, received_json): try: DATA_QUEUE_TRANSMIT.put(["SEND_TEST_FRAME"]) diff --git a/modem/static.py b/modem/static.py index 83d8149d..0de1ddc1 100644 --- a/modem/static.py +++ b/modem/static.py @@ -50,7 +50,8 @@ class ARQ: @dataclass class AudioParam: - tx_audio_level: int = 50 + tx_audio_level: int = 0 + rx_audio_level: int = 0 audio_input_devices = [] audio_output_devices = [] audio_input_device: int = -2 From b0d2c195aec9eef5167087f198bb3b8293c549f4 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Tue, 31 Oct 2023 16:45:18 +0100 Subject: [PATCH 07/10] fixed daemon startup --- gui/src/js/sock.js | 4 ++-- modem/config.py | 4 ++-- modem/daemon.py | 18 ++++++++---------- modem/sock.py | 9 ++++++--- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/gui/src/js/sock.js b/gui/src/js/sock.js index 643d886a..50836600 100644 --- a/gui/src/js/sock.js +++ b/gui/src/js/sock.js @@ -173,8 +173,8 @@ client.on("data", function (socketdata) { stateStore.arq_state = data["arq_state"]; stateStore.mode = data["mode"]; stateStore.bandwidth = data["bandwidth"]; - stateStore.tx_audio_level = data["audio_level"]; - stateStore.rx_audio_level = data["audio_level"]; + stateStore.tx_audio_level = data["tx_audio_level"]; + stateStore.rx_audio_level = data["rx_audio_level"]; // if audio level is different from config one, send new audio level to modem //console.log(parseInt(stateStore.tx_audio_level)) //console.log(parseInt(settings.tx_audio_level)) diff --git a/modem/config.py b/modem/config.py index 6d746013..cce08deb 100644 --- a/modem/config.py +++ b/modem/config.py @@ -58,7 +58,7 @@ class CONFIG: 'rx': data[3], 'tx': data[4], 'txaudiolevel': data[14], - 'rxaudiolevel': data[26], + 'rxaudiolevel': data[25], 'auto_tune': data[19] } @@ -79,7 +79,7 @@ class CONFIG: 'stats': data[19], 'fsk': data[13], 'tx_delay': data[21], - 'transmit_morse_identifier' : data[25] + 'transmit_morse_identifier' : data[26] } self.config['TCI'] = {'#TCI settings': None, 'ip': data[22], diff --git a/modem/daemon.py b/modem/daemon.py index c6aec992..c24769e5 100755 --- a/modem/daemon.py +++ b/modem/daemon.py @@ -212,7 +212,7 @@ class DAEMON: data[13] hamlib_rigctld_ip, data[14] hamlib_rigctld_path, data[15] hamlib_rigctld_server_port, - data[16] hamlib_rigctld_custom_args + data[16] hamlib_rigctld_custom_args """ self.start_rigctld(data) @@ -417,11 +417,8 @@ class DAEMON: except Exception as err: self.log.warning("[DMN] err starting rigctld: ", e=err) - - def start_modem(self, data): self.log.warning("[DMN] Starting Modem", rig=data[5], port=data[6]) - # list of parameters, necessary for running subprocess command as a list options = ["--port", str(DAEMON.port - 1)] @@ -492,17 +489,18 @@ class DAEMON: options.append(data[21]) #Mesh - print(data[24]) if data[24] == "True": options.append("--mesh") - #Morse identifier - print(data[25]) - if data[25] == "True": - options.append("--morse") + options.append("--rx-audio-level") - options.append(data[26]) + options.append(data[25]) + + #Morse identifier + if data[26] == "True": + options.append("--morse") + # safe data to config file config.write_entire_config(data) diff --git a/modem/sock.py b/modem/sock.py index d00b4cbb..17a56418 100644 --- a/modem/sock.py +++ b/modem/sock.py @@ -1096,7 +1096,8 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): low_bandwidth_mode = str(helpers.return_key_from_object("False", startparam, "low_bandwidth_mode")) tuning_range_fmin = str(helpers.return_key_from_object("-50", startparam, "tuning_range_fmin")) tuning_range_fmax = str(helpers.return_key_from_object("50", startparam, "tuning_range_fmax")) - tx_audio_level = str(helpers.return_key_from_object("100", startparam, "tx_audio_level")) + tx_audio_level = str(helpers.return_key_from_object("0", startparam, "tx_audio_level")) + rx_audio_level = str(helpers.return_key_from_object("0", startparam, "rx_audio_level")) respond_to_cq = str(helpers.return_key_from_object("False", startparam, "respond_to_cq")) rx_buffer_size = str(helpers.return_key_from_object("16", startparam, "rx_buffer_size")) enable_explorer = str(helpers.return_key_from_object("False", startparam, "enable_explorer")) @@ -1149,7 +1150,8 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): tx_delay, tci_ip, tci_port, - enable_mesh + enable_mesh, + rx_audio_level, ] ) command_response("start_modem", True) @@ -1363,7 +1365,8 @@ def send_modem_state(): "rf_level": str(HamlibParam.hamlib_rf), "strength": str(HamlibParam.hamlib_strength), "alc": str(HamlibParam.alc), - "audio_level": str(AudioParam.tx_audio_level), + "tx_audio_level": str(AudioParam.tx_audio_level), + "rx_audio_level": str(AudioParam.tx_audio_level), "audio_auto_tune": str(AudioParam.audio_auto_tune), "speed_level": str(ARQ.arq_speed_level), "mode": str(HamlibParam.hamlib_mode), From 5d7508dbede9cb9607f07738b9f9072491e6412e Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Tue, 31 Oct 2023 22:28:19 +0100 Subject: [PATCH 08/10] test with early retransmit of NACK --- modem/data_handler.py | 28 ++++++++++++++++++++++++---- modem/static.py | 8 ++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/modem/data_handler.py b/modem/data_handler.py index 084283a2..d661e734 100644 --- a/modem/data_handler.py +++ b/modem/data_handler.py @@ -3337,15 +3337,35 @@ class DATA: # timeout is reached, if we didnt receive data, while we waited # for the corresponding data frame + the transmitted signalling frame of ack/nack # + a small offset of about 1 second - timeout = self.burst_last_received + (self.time_list[self.speed_level] * frames_left) + self.duration_sig0_frame + self.channel_busy_timeout + 1 + timeout = \ + ( + self.burst_last_received + + (self.time_list[self.speed_level] * frames_left) + + self.duration_sig0_frame + + self.channel_busy_timeout + + 1 + ) + + + # override calculation + # if we reached 2/3 of the waiting time and didnt received a signal + # then send NACK earlier + time_left = timeout - time.time() + waiting_time = (self.time_list[self.speed_level] * frames_left) + self.duration_sig0_frame + self.channel_busy_timeout + 1 + timeout_percent = 100 - (time_left / waiting_time * 100) + #timeout_percent = 0 + if timeout_percent >= 75 and not ModemParam.is_codec2_traffic and not Modem.transmitting: + override = True + else: + override = False # TODO Enable this for development - print(f"timeout expected in:{round(timeout - time.time())} | frames left: {frames_left} of {self.rx_n_frames_per_burst} | speed level: {self.speed_level}") - # if timeout is expired, but we are receiving codec2 data, + print(f"timeout expected in:{round(timeout - time.time())} | timeout percent: {timeout_percent} | frames left: {frames_left} of {self.rx_n_frames_per_burst} | speed level: {self.speed_level}") + # if timeout is expired, but we are receivingt codec2 data, # better wait some more time because data might be important for us # reason for this situation can be delays on IRS and ISS, maybe because both had a busy channel condition. # Nevertheless, we need to keep timeouts short for efficiency - if timeout <= time.time() or modem_error_state and not ModemParam.is_codec2_traffic and not Modem.transmitting: + if timeout <= time.time() or modem_error_state and not ModemParam.is_codec2_traffic and not Modem.transmitting or override: self.log.warning( "[Modem] Burst decoding error or timeout", attempt=self.n_retries_per_burst, diff --git a/modem/static.py b/modem/static.py index 0de1ddc1..30042845 100644 --- a/modem/static.py +++ b/modem/static.py @@ -117,10 +117,10 @@ class ModemParam: @dataclass class Station: - mycallsign: bytes = b"AA0AA" + mycallsign: bytes = b"AA0AA-0" mycallsign_crc: bytes = b"A" - dxcallsign: bytes = b"ZZ9YY" - dxcallsign_crc: bytes = b"A" + dxcallsign: bytes = b"ZZ9YY-0" + dxcallsign_crc: bytes = b"B" mygrid: bytes = b"" dxgrid: bytes = b"" ssid_list = [] # ssid list we are responding to @@ -137,7 +137,7 @@ class TCIParam: @dataclass class Modem: - version = "0.11.2-alpha.2" + version = "0.11.2-alpha.3" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds From 0c23ca05613f2835c7b45f2f2fb2e7f1105e3be0 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Thu, 2 Nov 2023 15:02:47 +0100 Subject: [PATCH 09/10] make channel opener more random --- modem/data_handler.py | 9 ++++++--- modem/static.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/modem/data_handler.py b/modem/data_handler.py index d661e734..bef01922 100644 --- a/modem/data_handler.py +++ b/modem/data_handler.py @@ -2246,14 +2246,17 @@ class DATA: ) # Let's check if we have a busy channel and if we are not in a running arq session. - if ModemParam.channel_busy and not ARQ.arq_state_event.is_set(): + if ModemParam.channel_busy and not ARQ.arq_state_event.is_set() or ModemParam.is_codec2_traffic: self.channel_busy_handler() # if channel free, enqueue frame for tx - self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) + if not ARQ.arq_state_event.is_set(): + self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) # wait until timeout or event set - ARQ.arq_state_event.wait(timeout=self.datachannel_opening_interval) + + random_wait_time = randrange(int(self.duration_sig1_frame * 10), int(self.datachannel_opening_interval * 10), 5) + ARQ.arq_state_event.wait(timeout=random_wait_time) if ARQ.arq_state_event.is_set(): return True diff --git a/modem/static.py b/modem/static.py index 30042845..373d86dc 100644 --- a/modem/static.py +++ b/modem/static.py @@ -137,7 +137,7 @@ class TCIParam: @dataclass class Modem: - version = "0.11.2-alpha.3" + version = "0.11.2-alpha.4" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds From e95f9a1d6ad36e4bbcffaf47ea3a0864196440a9 Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Thu, 2 Nov 2023 15:07:36 +0100 Subject: [PATCH 10/10] make channel opener more random --- modem/data_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modem/data_handler.py b/modem/data_handler.py index bef01922..484502e1 100644 --- a/modem/data_handler.py +++ b/modem/data_handler.py @@ -2255,7 +2255,7 @@ class DATA: # wait until timeout or event set - random_wait_time = randrange(int(self.duration_sig1_frame * 10), int(self.datachannel_opening_interval * 10), 5) + random_wait_time = randrange(int(self.duration_sig1_frame * 10), int(self.datachannel_opening_interval * 10), 1) / 10 ARQ.arq_state_event.wait(timeout=random_wait_time) if ARQ.arq_state_event.is_set():