Merge branch 'develop' into qm-develop-gui

This commit is contained in:
DJ2LS 2023-11-04 14:33:09 +01:00 committed by GitHub
commit 22e92af2ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 406 additions and 309 deletions

View file

@ -17,17 +17,22 @@ import {
getNewMessagesByDXCallsign, getNewMessagesByDXCallsign,
} from "../js/chatHandler"; } from "../js/chatHandler";
import { sendTestFrame, setTxAudioLevel } from "../js/sock.js"; import { sendTestFrame, setTxAudioLevel, setRxAudioLevel } from "../js/sock.js";
function tuneAudio() { function tuneAudio() {
sendTestFrame(); sendTestFrame();
} }
function set_audio_level() { function set_tx_audio_level() {
saveSettingsToFile(); saveSettingsToFile();
setTxAudioLevel(settings.tx_audio_level); setTxAudioLevel(settings.tx_audio_level);
} }
function set_rx_audio_level() {
saveSettingsToFile();
setRxAudioLevel(settings.rx_audio_level);
}
function deleteChat() { function deleteChat() {
//console.log(chat.selectedCallsign) //console.log(chat.selectedCallsign)
deleteChatByCallsign(chat.selectedCallsign); deleteChatByCallsign(chat.selectedCallsign);
@ -1194,6 +1199,21 @@ const transmissionSpeedChartDataMessageInfo = computed(() => ({
Transmit Transmit
</button> </button>
</div> </div>
<div class="input-group input-group-sm mb-1">
<span class="input-group-text">RX Level</span>
<span class="input-group-text">{{ settings.rx_audio_level }}</span>
<span class="input-group-text w-75">
<input
type="range"
class="form-range"
min="-30"
max="20"
step="1"
id="audioLevelRX"
@click="set_rx_audio_level()"
v-model="settings.rx_audio_level"
/></span>
</div>
<div class="input-group input-group-sm mb-1"> <div class="input-group input-group-sm mb-1">
<span class="input-group-text">TX Level</span> <span class="input-group-text">TX Level</span>
<span class="input-group-text">{{ settings.tx_audio_level }}</span> <span class="input-group-text">{{ settings.tx_audio_level }}</span>
@ -1201,11 +1221,11 @@ const transmissionSpeedChartDataMessageInfo = computed(() => ({
<input <input
type="range" type="range"
class="form-range" class="form-range"
min="0" min="-30"
max="250" max="20"
step="1" step="1"
id="audioLevelTX" id="audioLevelTX"
@click="set_audio_level()" @click="set_tx_audio_level()"
v-model="settings.tx_audio_level" v-model="settings.tx_audio_level"
/></span> /></span>
</div> </div>

View file

@ -195,6 +195,7 @@ export function startModem() {
tuning_range_fmin: settings.tuning_range_fmin, tuning_range_fmin: settings.tuning_range_fmin,
tuning_range_fmax: settings.tuning_range_fmax, tuning_range_fmax: settings.tuning_range_fmax,
tx_audio_level: settings.tx_audio_level, tx_audio_level: settings.tx_audio_level,
rx_audio_level: settings.rx_audio_level,
respond_to_cq: settings.respond_to_cq, respond_to_cq: settings.respond_to_cq,
rx_buffer_size: settings.rx_buffer_size, rx_buffer_size: settings.rx_buffer_size,
enable_explorer: settings.enable_explorer, enable_explorer: settings.enable_explorer,

View file

@ -55,7 +55,8 @@ const configDefaultSettings =
"daemon_port": 3001,\ "daemon_port": 3001,\
"rx_audio" : "",\ "rx_audio" : "",\
"tx_audio" : "",\ "tx_audio" : "",\
"tx_audio_level" : 100,\ "tx_audio_level" : 0,\
"rx_audio_level" : 0,\
"mycall": "AA0AA-0",\ "mycall": "AA0AA-0",\
"myssid": "0",\ "myssid": "0",\
"mygrid": "JN40aa",\ "mygrid": "JN40aa",\

View file

@ -31,6 +31,8 @@ const split_char = "0;1;";
// global to keep track of Modem connection error emissions // global to keep track of Modem connection error emissions
var modemShowConnectStateError = 1; var modemShowConnectStateError = 1;
var setTxAudioLevelOnce = true; var setTxAudioLevelOnce = true;
var setRxAudioLevelOnce = true;
// network connection Timeout // network connection Timeout
setTimeout(connectModem, 2000); setTimeout(connectModem, 2000);
@ -171,7 +173,8 @@ client.on("data", function (socketdata) {
stateStore.arq_state = data["arq_state"]; stateStore.arq_state = data["arq_state"];
stateStore.mode = data["mode"]; stateStore.mode = data["mode"];
stateStore.bandwidth = data["bandwidth"]; stateStore.bandwidth = data["bandwidth"];
stateStore.tx_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 // if audio level is different from config one, send new audio level to modem
//console.log(parseInt(stateStore.tx_audio_level)) //console.log(parseInt(stateStore.tx_audio_level))
//console.log(parseInt(settings.tx_audio_level)) //console.log(parseInt(settings.tx_audio_level))
@ -185,6 +188,16 @@ client.on("data", function (socketdata) {
setTxAudioLevel(settings.tx_audio_level); 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.dbfs_level = data["audio_dbfs"];
stateStore.ptt_state = data["ptt_state"]; stateStore.ptt_state = data["ptt_state"];
stateStore.speed_level = data["speed_level"]; stateStore.speed_level = data["speed_level"];
@ -541,6 +554,11 @@ export function setTxAudioLevel(value) {
'{"type" : "set", "command" : "tx_audio_level", "value" : "' + value + '"}'; '{"type" : "set", "command" : "tx_audio_level", "value" : "' + value + '"}';
writeTncCommand(command); writeTncCommand(command);
} }
export function setRxAudioLevel(value) {
var command =
'{"type" : "set", "command" : "rx_audio_level", "value" : "' + value + '"}';
writeTncCommand(command);
}
// Send Message // Send Message
export function sendMessage(obj) { export function sendMessage(obj) {

View file

@ -6,6 +6,7 @@ export const useSettingsStore = defineStore("settingsStore", () => {
var tx_audio = ref(); var tx_audio = ref();
var rx_audio = ref(); var rx_audio = ref();
var tx_audio_level = ref(); var tx_audio_level = ref();
var rx_audio_level = ref();
// network // network
var modem_host = ref("127.0.0.1"); var modem_host = ref("127.0.0.1");
@ -159,6 +160,7 @@ export const useSettingsStore = defineStore("settingsStore", () => {
tx_audio: tx_audio.value, tx_audio: tx_audio.value,
rx_audio: rx_audio.value, rx_audio: rx_audio.value,
tx_audio_level: tx_audio_level.value, tx_audio_level: tx_audio_level.value,
rx_audio_level: rx_audio_level.value,
}; };
return config_export; return config_export;
@ -227,5 +229,6 @@ export const useSettingsStore = defineStore("settingsStore", () => {
getSerialDevices, getSerialDevices,
serial_devices, serial_devices,
tx_audio_level, tx_audio_level,
rx_audio_level,
}; };
}); });

View file

@ -41,6 +41,8 @@ export const useStateStore = defineStore("stateStore", () => {
var hamlib_status = ref(""); var hamlib_status = ref("");
var tx_audio_level = ref(""); var tx_audio_level = ref("");
var rx_audio_level = ref("");
var alc = ref(""); var alc = ref("");
var is_codec2_traffic = ref(""); var is_codec2_traffic = ref("");
@ -115,6 +117,7 @@ export const useStateStore = defineStore("stateStore", () => {
audio_recording, audio_recording,
hamlib_status, hamlib_status,
tx_audio_level, tx_audio_level,
rx_audio_level,
alc, alc,
updateTncState, updateTncState,
arq_transmission_percent, arq_transmission_percent,

View file

@ -58,6 +58,7 @@ class CONFIG:
'rx': data[3], 'rx': data[3],
'tx': data[4], 'tx': data[4],
'txaudiolevel': data[14], 'txaudiolevel': data[14],
'rxaudiolevel': data[25],
'auto_tune': data[19] 'auto_tune': data[19]
} }
@ -77,14 +78,15 @@ class CONFIG:
'explorer': data[17], 'explorer': data[17],
'stats': data[19], 'stats': data[19],
'fsk': data[13], 'fsk': data[13],
'tx_delay': data[21] 'tx_delay': data[21],
} 'transmit_morse_identifier' : data[26]
}
self.config['TCI'] = {'#TCI settings': None, self.config['TCI'] = {'#TCI settings': None,
'ip': data[22], 'ip': data[22],
'port': data[23] 'port': data[23]
} }
self.config['MESH'] = {'#TCI settings': None, self.config['MESH'] = {'#Mesh settings': None,
'enable_protocol': data[24] 'enable_protocol': data[24]
} }

View file

@ -6,8 +6,10 @@ import numpy as np
""" """
class MorseCodePlayer: class MorseCodePlayer:
def __init__(self, wpm=150, f=1500, fs=48000): def __init__(self, wpm=25, f=1500, fs=48000):
self.wpm = wpm self.wpm = wpm
self.f0 = f self.f0 = f
self.fs = fs self.fs = fs
@ -39,28 +41,26 @@ class MorseCodePlayer:
signal = np.array([], dtype=np.int16) signal = np.array([], dtype=np.int16)
for char in morse: for char in morse:
if char == '.': if char == '.':
duration = int(self.dot_duration * self.fs) duration = self.dot_duration # Using class-defined duration
s = np.sin(2 * np.pi * self.f0 * np.arange(duration) / self.fs) t = np.linspace(0, duration, int(self.fs * duration), endpoint=False)
signal = np.concatenate((signal, s * 32767)) s = 0.5 * np.sin(2 * np.pi * self.f0 * t)
pause_duration = int(self.pause_duration * self.fs) signal = np.concatenate((signal, np.int16(s * 32767)))
signal = np.concatenate((signal, np.zeros(pause_duration, dtype=np.int16))) pause_samples = int(self.pause_duration * self.fs)
signal = np.concatenate((signal, np.zeros(pause_samples, dtype=np.int16)))
elif char == '-': elif char == '-':
duration = int(self.dash_duration * self.fs) duration = self.dash_duration # Using class-defined duration
s = np.sin(2 * np.pi * self.f0 * np.arange(duration) / self.fs) t = np.linspace(0, duration, int(self.fs * duration), endpoint=False)
signal = np.concatenate((signal, s * 32767)) s = 0.5 * np.sin(2 * np.pi * self.f0 * t)
pause_duration = int(self.pause_duration * self.fs) signal = np.concatenate((signal, np.int16(s * 32767)))
signal = np.concatenate((signal, np.zeros(pause_duration, dtype=np.int16))) pause_samples = int(self.pause_duration * self.fs)
signal = np.concatenate((signal, np.zeros(pause_samples, dtype=np.int16)))
elif char == ' ': elif char == ' ':
pause_duration = int(self.word_pause_duration * self.fs) pause_samples = int(self.word_pause_duration * self.fs)
signal = np.concatenate((signal, np.zeros(pause_duration, dtype=np.int16))) signal = np.concatenate((signal, np.zeros(pause_samples, dtype=np.int16)))
pause_duration = int(self.pause_duration * self.fs) pause_samples = int(self.pause_duration * self.fs)
signal = np.concatenate((signal, np.zeros(pause_duration, dtype=np.int16))) signal = np.concatenate((signal, np.zeros(pause_samples, 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)
return signal return signal

View file

@ -212,7 +212,7 @@ class DAEMON:
data[13] hamlib_rigctld_ip, data[13] hamlib_rigctld_ip,
data[14] hamlib_rigctld_path, data[14] hamlib_rigctld_path,
data[15] hamlib_rigctld_server_port, data[15] hamlib_rigctld_server_port,
data[16] hamlib_rigctld_custom_args data[16] hamlib_rigctld_custom_args
""" """
self.start_rigctld(data) self.start_rigctld(data)
@ -417,11 +417,8 @@ class DAEMON:
except Exception as err: except Exception as err:
self.log.warning("[DMN] err starting rigctld: ", e=err) self.log.warning("[DMN] err starting rigctld: ", e=err)
def start_modem(self, data): def start_modem(self, data):
self.log.warning("[DMN] Starting Modem", rig=data[5], port=data[6]) self.log.warning("[DMN] Starting Modem", rig=data[5], port=data[6])
# list of parameters, necessary for running subprocess command as a list # list of parameters, necessary for running subprocess command as a list
options = ["--port", str(DAEMON.port - 1)] options = ["--port", str(DAEMON.port - 1)]
@ -492,10 +489,19 @@ class DAEMON:
options.append(data[21]) options.append(data[21])
#Mesh #Mesh
print(data[24])
if data[24] == "True": if data[24] == "True":
options.append("--mesh") options.append("--mesh")
options.append("--rx-audio-level")
options.append(data[25])
#Morse identifier
if data[26] == "True":
options.append("--morse")
# safe data to config file # safe data to config file
config.write_entire_config(data) config.write_entire_config(data)

View file

@ -56,10 +56,16 @@ class DATA:
self.length_sig0_frame = 14 self.length_sig0_frame = 14
self.length_sig1_frame = 14 self.length_sig1_frame = 14
# duration of signalling frame # duration of frames
self.duration_sig0_frame = 2.3 self.duration_datac4 = 5.17
self.duration_sig1_frame = 2.3 self.duration_datac13 = 2.0
self.longest_duration = 5.8 # datac5 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 # hold session id
self.session_id = bytes(1) self.session_id = bytes(1)
@ -110,7 +116,6 @@ class DATA:
self.received_LOW_BANDWIDTH_MODE = False self.received_LOW_BANDWIDTH_MODE = False
self.data_channel_max_retries = 15 self.data_channel_max_retries = 15
self.datachannel_timeout = False
# -------------- AVAILABLE MODES START----------- # -------------- AVAILABLE MODES START-----------
# IMPORTANT: LISTS MUST BE OF EQUAL LENGTH # IMPORTANT: LISTS MUST BE OF EQUAL LENGTH
@ -124,7 +129,7 @@ class DATA:
# List for minimum SNR operating level for the corresponding mode in self.mode_list # List for minimum SNR operating level for the corresponding mode in self.mode_list
self.snr_list_low_bw = [-100] self.snr_list_low_bw = [-100]
# List for time to wait for corresponding mode in seconds # 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 # --------------------- HIGH BANDWIDTH
@ -140,7 +145,7 @@ class DATA:
# test with 6,7 --> caused sometimes a frame timeout if ack frame takes longer # 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 Need to check why ACK frames needs more time
# TODO Adjust these times # 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----------- # -------------- AVAILABLE MODES END-----------
# Mode list for selecting between low bandwidth ( 500Hz ) and modes with higher bandwidth # Mode list for selecting between low bandwidth ( 500Hz ) and modes with higher bandwidth
@ -182,6 +187,8 @@ class DATA:
self.data_frame_ack_timeout_seconds = 4.5 # timeout for data frame acknowledges self.data_frame_ack_timeout_seconds = 4.5 # timeout for data frame acknowledges
self.rpt_ack_timeout_seconds = 4.5 # timeout for rpt frame acknowledges self.rpt_ack_timeout_seconds = 4.5 # timeout for rpt frame acknowledges
self.transmission_timeout = 180 # transmission timeout in seconds self.transmission_timeout = 180 # transmission timeout in seconds
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 # Dictionary of functions and log messages used in process_data
# instead of a long series of if-elif-else statements. # instead of a long series of if-elif-else statements.
@ -546,10 +553,9 @@ class DATA:
ack_frame[3:4] = bytes([int(self.speed_level)]) ack_frame[3:4] = bytes([int(self.speed_level)])
ack_frame[4:8] = len(ARQ.rx_frame_buffer).to_bytes(4, byteorder="big") ack_frame[4:8] = len(ARQ.rx_frame_buffer).to_bytes(4, byteorder="big")
# wait while timeout not reached and our busy state is busy # wait if we have a channel busy condition
channel_busy_timeout = time.time() + 5 if ModemParam.channel_busy:
while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): self.channel_busy_handler()
threading.Event().wait(0.01)
# Transmit frame # Transmit frame
self.enqueue_frame_for_tx([ack_frame], c2_mode=FREEDV_MODE.sig1.value) self.enqueue_frame_for_tx([ack_frame], c2_mode=FREEDV_MODE.sig1.value)
@ -562,16 +568,11 @@ class DATA:
ack_frame[1:2] = self.session_id ack_frame[1:2] = self.session_id
ack_frame[2:3] = helpers.snr_to_bytes(snr) ack_frame[2:3] = helpers.snr_to_bytes(snr)
# wait while timeout not reached and our busy state is busy # wait if we have a channel busy condition
channel_busy_timeout = time.time() + 5 if ModemParam.channel_busy:
while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): self.channel_busy_handler()
threading.Event().wait(0.01)
# reset burst timeout in case we had to wait too long
self.burst_last_received = time.time() + channel_busy_timeout + 8
# Transmit frame # 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) 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: def send_retransmit_request_frame(self) -> None:
@ -607,22 +608,20 @@ class DATA:
# TODO Do we have to send ident frame? # TODO Do we have to send ident frame?
# self.enqueue_frame_for_tx([ack_frame, self.send_ident_frame(False)], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) # self.enqueue_frame_for_tx([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 # wait if we have a channel busy condition
channel_busy_timeout = time.time() + 5 if ModemParam.channel_busy:
while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): self.channel_busy_handler()
threading.Event().wait(0.01)
self.enqueue_frame_for_tx([nack_frame], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) self.enqueue_frame_for_tx([nack_frame], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0)
# reset burst timeout in case we had to wait too long # reset burst timeout in case we had to wait too long
self.burst_last_received = time.time() 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""" """Build and send NACK frame for watchdog timeout"""
# increment nack counter for transmission stats # increment nack counter for transmission stats
self.frame_nack_counter += 1 self.frame_nack_counter += 1
# we need to clear our rx burst buffer # we need to clear our rx burst buffer
ARQ.rx_burst_buffer = [] ARQ.rx_burst_buffer = []
@ -631,16 +630,14 @@ class DATA:
nack_frame = bytearray(self.length_sig1_frame) nack_frame = bytearray(self.length_sig1_frame)
nack_frame[:1] = bytes([FR_TYPE.BURST_NACK.value]) nack_frame[:1] = bytes([FR_TYPE.BURST_NACK.value])
nack_frame[1:2] = self.session_id 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[3:4] = bytes([int(self.speed_level)])
nack_frame[4:5] = bytes([int(tx_n_frames_per_burst)]) 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") nack_frame[5:9] = len(ARQ.rx_frame_buffer).to_bytes(4, byteorder="big")
# wait if we have a channel busy condition
# wait while timeout not reached and our busy state is busy if ModemParam.channel_busy:
channel_busy_timeout = time.time() + 5 + 5 self.channel_busy_handler()
while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot():
threading.Event().wait(0.01)
# TRANSMIT NACK FRAME FOR BURST # TRANSMIT NACK FRAME FOR BURST
self.enqueue_frame_for_tx([nack_frame], c2_mode=FREEDV_MODE.sig1.value, copies=1, repeat_delay=0) self.enqueue_frame_for_tx([nack_frame], c2_mode=FREEDV_MODE.sig1.value, copies=1, repeat_delay=0)
@ -654,16 +651,10 @@ class DATA:
disconnection_frame[:1] = bytes([FR_TYPE.ARQ_SESSION_CLOSE.value]) disconnection_frame[:1] = bytes([FR_TYPE.ARQ_SESSION_CLOSE.value])
disconnection_frame[1:2] = self.session_id disconnection_frame[1:2] = self.session_id
disconnection_frame[2:5] = Station.dxcallsign_crc disconnection_frame[2:5] = Station.dxcallsign_crc
# TODO Needed? disconnection_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign)
# self.enqueue_frame_for_tx([disconnection_frame, self.send_ident_frame(False)], c2_mode=FREEDV_MODE.sig0.value, copies=5, repeat_delay=0)
# TODO We need to add the ident frame feature with a seperate PR after publishing latest protocol
# 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 # wait if we have a channel busy condition
channel_busy_timeout = time.time() + 5 if ModemParam.channel_busy:
while ModemParam.channel_busy and time.time() < channel_busy_timeout and not self.check_if_mode_fits_to_busy_slot(): self.channel_busy_handler()
threading.Event().wait(0.01)
self.enqueue_frame_for_tx([disconnection_frame], c2_mode=FREEDV_MODE.sig0.value, copies=3, repeat_delay=0) self.enqueue_frame_for_tx([disconnection_frame], c2_mode=FREEDV_MODE.sig0.value, copies=3, repeat_delay=0)
@ -815,8 +806,12 @@ class DATA:
): ):
self.arq_calculate_speed_level(snr) self.arq_calculate_speed_level(snr)
self.data_channel_last_received = int(time.time()) + 6 + 6 # TIMING TEST
self.burst_last_received = int(time.time()) + 6 + 6 #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 # Create and send ACK frame
self.log.info("[Modem] ARQ | RX | SENDING ACK", finished=ARQ.arq_seconds_until_finish, self.log.info("[Modem] ARQ | RX | SENDING ACK", finished=ARQ.arq_seconds_until_finish,
bytesperminute=ARQ.bytes_per_minute) bytesperminute=ARQ.bytes_per_minute)
@ -1008,9 +1003,7 @@ class DATA:
mode_slots=mode_slots, mode_slots=mode_slots,
) )
return False return False
return True
else:
return True
def arq_calculate_speed_level(self, snr): def arq_calculate_speed_level(self, snr):
current_speed_level = self.speed_level current_speed_level = self.speed_level
@ -1041,6 +1034,13 @@ class DATA:
# Update modes we are listening to # Update modes we are listening to
self.set_listening_modes(False, True, self.mode_list[self.speed_level]) 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): def arq_process_received_data_frame(self, data_frame, snr, signed):
""" """
@ -1742,8 +1742,6 @@ class DATA:
Station.dxcallsign = self.dxcallsign Station.dxcallsign = self.dxcallsign
Station.dxcallsign_crc = helpers.get_crc_24(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( self.log.info(
"[Modem] SESSION [" "[Modem] SESSION ["
+ str(self.mycallsign, "UTF-8") + str(self.mycallsign, "UTF-8")
@ -1753,35 +1751,9 @@ class DATA:
state=ARQ.arq_session_state, 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: if ModemParam.channel_busy:
self.log.warning("[Modem] Channel busy, waiting until free...") self.channel_busy_handler()
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.open_session() self.open_session()
@ -1995,6 +1967,10 @@ class DATA:
# we need to send disconnect frame before doing arq cleanup # we need to send disconnect frame before doing arq cleanup
# we would lose our session id then # we would lose our session id then
self.send_disconnect_frame() 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() self.arq_cleanup()
def received_session_close(self, data_in: bytes): def received_session_close(self, data_in: bytes):
@ -2162,114 +2138,19 @@ class DATA:
if ARQ.arq_session: if ARQ.arq_session:
threading.Event().wait(2.5) threading.Event().wait(2.5)
self.datachannel_timeout = False # init arq state event
ARQ.arq_state_event = threading.Event()
# 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))
# finally start the channel opening procedure
self.arq_open_data_channel(mycallsign) self.arq_open_data_channel(mycallsign)
# wait until data channel is open # if data channel is open, return true else false
while not ARQ.arq_state and not self.datachannel_timeout and Modem.modem_state in ["BUSY"]: if ARQ.arq_state_event.is_set():
threading.Event().wait(0.01) # start arq transmission
if ARQ.arq_state:
self.arq_transmit(data_out, hmac_salt) self.arq_transmit(data_out, hmac_salt)
return True 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: 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( self.log.debug(
"[Modem] arq_open_data_channel:", transmission_uuid=self.transmission_uuid "[Modem] arq_open_data_channel:", transmission_uuid=self.transmission_uuid
) )
@ -2297,16 +2178,93 @@ class DATA:
+ str(self.dxcallsign, "UTF-8") + str(self.dxcallsign, "UTF-8")
+ "]" + "]"
) )
self.datachannel_timeout = True
# Attempt to clean up the far-side, if it received the # Attempt to clean up the far-side, if it received the
# open_session frame and can still hear us. # open_session frame and can still hear us.
self.close_session() self.close_session()
# otherwise return false
return False return False
# Shouldn't get here... def arq_open_data_channel(
return True 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() or ModemParam.is_codec2_traffic:
self.channel_busy_handler()
# if channel free, enqueue frame for tx
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
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():
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): def arq_received_data_channel_opener(self, data_in: bytes):
""" """
@ -2338,7 +2296,7 @@ class DATA:
# Station B already tries connecting to Station A. # Station B already tries connecting to Station A.
# For avoiding ignoring repeated connect request in case of packet loss # For avoiding ignoring repeated connect request in case of packet loss
# we are only ignoring packets in case we are ISS # 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 return False
self.is_IRS = True self.is_IRS = True
@ -2356,9 +2314,6 @@ class DATA:
irs=helpers.bool_to_string(self.is_IRS) 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") frametype = int.from_bytes(bytes(data_in[:1]), "big")
# check if we received low bandwidth mode # check if we received low bandwidth mode
# possible channel constellations # possible channel constellations
@ -2406,29 +2361,14 @@ class DATA:
# initially set speed_level 0 in case of bad SNR and no matching mode # initially set speed_level 0 in case of bad SNR and no matching mode
self.speed_level = 0 self.speed_level = 0
# TODO MOVE THIS TO arq_calculate_speed_level() # calculate initial speed level in correlation to latest known SNR
# calculate speed level in correlation to latest known SNR
for i in range(len(self.mode_list)): for i in range(len(self.mode_list)):
if ModemParam.snr >= self.snr_list[i]: if ModemParam.snr >= self.snr_list[i]:
self.speed_level = i self.speed_level = i
# calculate if speed level fits to busy condition # check if speed level fits to busy condition
mode_name = codec2.FREEDV_MODE(self.mode_list[self.speed_level]).name if not self.check_if_mode_fits_to_busy_slot():
mode_slots = codec2.FREEDV_MODE_USED_SLOTS[mode_name].value
if mode_slots in [ModemParam.channel_busy_slot]:
self.speed_level = 0 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 # Update modes we are listening to
self.set_listening_modes(True, True, self.mode_list[self.speed_level]) self.set_listening_modes(True, True, self.mode_list[self.speed_level])
@ -2457,6 +2397,7 @@ class DATA:
) )
# Reset data_channel/burst timestamps # Reset data_channel/burst timestamps
# TIMING TEST
self.data_channel_last_received = int(time.time()) 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 self.burst_last_received = int(time.time() + 10) # we might need some more time so lets increase this
@ -2505,7 +2446,9 @@ class DATA:
# set start of transmission for our statistics # set start of transmission for our statistics
self.rx_start_of_transmission = time.time() self.rx_start_of_transmission = time.time()
# TIMING TEST
# Reset data_channel/burst timestamps once again for avoiding running into timeout # 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.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 self.burst_last_received = int(time.time() + 10) # we might need some more time so lets increase this
@ -2539,10 +2482,10 @@ class DATA:
self.time_list = self.time_list_high_bw self.time_list = self.time_list_high_bw
self.log.debug("[Modem] high bandwidth mode", modes=self.mode_list) 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.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.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'------' Station.dxgrid = b'------'
helpers.add_to_heard_stations( helpers.add_to_heard_stations(
Station.dxcallsign, Station.dxcallsign,
@ -2562,13 +2505,15 @@ class DATA:
snr=ModemParam.snr, 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 ARQ.arq_state = True
# also set the ARQ event
ARQ.arq_state_event.set()
# Update data_channel timestamp # Update data_channel timestamp
self.data_channel_last_received = int(time.time()) self.data_channel_last_received = int(time.time())
else: else:
Modem.modem_state = "IDLE"
ARQ.arq_state = False
self.send_data_to_socket_queue( self.send_data_to_socket_queue(
freedata="modem-message", freedata="modem-message",
arq="transmission", arq="transmission",
@ -2578,7 +2523,6 @@ class DATA:
dxcallsign=str(self.dxcallsign, 'UTF-8'), dxcallsign=str(self.dxcallsign, 'UTF-8'),
irs=helpers.bool_to_string(self.is_IRS) irs=helpers.bool_to_string(self.is_IRS)
) )
# TODO We should display a message to this effect on the UI.
self.log.warning( self.log.warning(
"[Modem] protocol version mismatch:", "[Modem] protocol version mismatch:",
received=protocol_version, received=protocol_version,
@ -2761,8 +2705,6 @@ class DATA:
""" """
self.log.warning("[Modem] Stopping transmission!") self.log.warning("[Modem] Stopping transmission!")
Modem.modem_state = "IDLE"
ARQ.arq_state = False
self.send_data_to_socket_queue( self.send_data_to_socket_queue(
freedata="modem-message", freedata="modem-message",
arq="transmission", arq="transmission",
@ -2778,7 +2720,6 @@ class DATA:
# TODO Not sure if we really need the session id when disconnecting # TODO Not sure if we really need the session id when disconnecting
# stop_frame[1:2] = self.session_id # stop_frame[1:2] = self.session_id
stop_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign) stop_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign)
self.enqueue_frame_for_tx([stop_frame], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0) self.enqueue_frame_for_tx([stop_frame], c2_mode=FREEDV_MODE.sig1.value, copies=3, repeat_delay=0)
self.arq_cleanup() self.arq_cleanup()
@ -2849,6 +2790,8 @@ class DATA:
else: else:
self.enqueue_frame_for_tx([beacon_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, self.enqueue_frame_for_tx([beacon_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1,
repeat_delay=0) 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 self.beacon_interval_timer = time.time() + self.beacon_interval
while ( while (
@ -2926,9 +2869,7 @@ class DATA:
self.enqueue_frame_for_tx([cq_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value) self.enqueue_frame_for_tx([cq_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value)
else: else:
self.enqueue_frame_for_tx([cq_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0) 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"])
def received_cq(self, data_in: bytes) -> None: def received_cq(self, data_in: bytes) -> None:
""" """
@ -3073,6 +3014,26 @@ 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",
channel="busy",
status="waiting",
)
# wait while timeout not reached and our busy state is busy
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)
# ------------ CALCULATE TRANSFER RATES # ------------ CALCULATE TRANSFER RATES
def calculate_transfer_rate_rx( def calculate_transfer_rate_rx(
self, rx_start_of_transmission: float, receivedbytes: int self, rx_start_of_transmission: float, receivedbytes: int
@ -3263,6 +3224,7 @@ class DATA:
ARQ.arq_session_state = "disconnected" ARQ.arq_session_state = "disconnected"
ARQ.speed_list = [] ARQ.speed_list = []
ARQ.arq_state = False ARQ.arq_state = False
ARQ.arq_state_event = threading.Event()
self.arq_file_transfer = False self.arq_file_transfer = False
Beacon.beacon_pause = False Beacon.beacon_pause = False
@ -3375,10 +3337,38 @@ class DATA:
if frames_left == 0: if frames_left == 0:
frames_left = 1 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
)
# 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 # 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}") 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 <= time.time() or modem_error_state: # 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 or override:
self.log.warning( self.log.warning(
"[Modem] Burst decoding error or timeout", "[Modem] Burst decoding error or timeout",
attempt=self.n_retries_per_burst, attempt=self.n_retries_per_burst,
@ -3389,7 +3379,7 @@ class DATA:
print( 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)}") 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: 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 # reset self.burst_last_received
self.burst_last_received = time.time() + self.time_list[self.speed_level] * frames_left self.burst_last_received = time.time() + self.time_list[self.speed_level] * frames_left
@ -3398,8 +3388,8 @@ class DATA:
else: else:
# reset self.burst_last_received # reset self.burst_last_received counter
self.burst_last_received = time.time() + self.time_list[self.speed_level] self.burst_last_received = time.time()
# reduce speed level if nack counter increased # reduce speed level if nack counter increased
self.frame_received_counter = 0 self.frame_received_counter = 0
@ -3420,13 +3410,14 @@ class DATA:
self.set_listening_modes(True, True, self.mode_list[self.speed_level]) 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? # 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 # Update data_channel timestamp
# TODO Disabled this one for testing. # TODO Disabled this one for testing.
# self.data_channel_last_received = time.time() # self.data_channel_last_received = time.time()
self.n_retries_per_burst += 1 self.n_retries_per_burst += 1
else: else:
# debugging output
# print((self.data_channel_last_received + self.time_list[self.speed_level])-time.time()) # print((self.data_channel_last_received + self.time_list[self.speed_level])-time.time())
pass pass

View file

@ -190,10 +190,17 @@ if __name__ == "__main__":
PARSER.add_argument( PARSER.add_argument(
"--tx-audio-level", "--tx-audio-level",
dest="tx_audio_level", dest="tx_audio_level",
default=50, default=0,
help="Set the tx audio level at an early stage", help="Set the tx audio level at an early stage",
type=int, 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( PARSER.add_argument(
"--rx-buffer-size", "--rx-buffer-size",
dest="rx_buffer_size", dest="rx_buffer_size",
@ -261,6 +268,15 @@ if __name__ == "__main__":
help="Enable and set hmac message salt", help="Enable and set hmac message salt",
) )
PARSER.add_argument(
"--morse",
dest="transmit_morse_identifier",
action="store_true",
default=False,
help="Enable and send a morse identifier on disconnect an beacon",
)
ARGS = PARSER.parse_args() ARGS = PARSER.parse_args()
# set save to folder state for allowing downloading files to local file system # set save to folder state for allowing downloading files to local file system
@ -306,6 +322,7 @@ if __name__ == "__main__":
ModemParam.tuning_range_fmin = ARGS.tuning_range_fmin ModemParam.tuning_range_fmin = ARGS.tuning_range_fmin
ModemParam.tuning_range_fmax = ARGS.tuning_range_fmax ModemParam.tuning_range_fmax = ARGS.tuning_range_fmax
AudioParam.tx_audio_level = ARGS.tx_audio_level 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 Modem.respond_to_cq = ARGS.enable_respond_to_cq
ARQ.rx_buffer_size = ARGS.rx_buffer_size ARQ.rx_buffer_size = ARGS.rx_buffer_size
Modem.enable_explorer = ARGS.enable_explorer Modem.enable_explorer = ARGS.enable_explorer
@ -316,7 +333,7 @@ if __name__ == "__main__":
ModemParam.tx_delay = ARGS.tx_delay ModemParam.tx_delay = ARGS.tx_delay
MeshParam.enable_protocol = ARGS.enable_mesh MeshParam.enable_protocol = ARGS.enable_mesh
Modem.enable_hmac = ARGS.enable_hmac Modem.enable_hmac = ARGS.enable_hmac
Modem.transmit_morse_identifier = ARGS.transmit_morse_identifier
except Exception as e: except Exception as e:
log.error("[DMN] Error reading config file", exception=e) log.error("[DMN] Error reading config file", exception=e)
@ -358,7 +375,8 @@ if __name__ == "__main__":
Modem.low_bandwidth_mode = conf.get('Modem', 'narrowband', 'False') Modem.low_bandwidth_mode = conf.get('Modem', 'narrowband', 'False')
ModemParam.tuning_range_fmin = float(conf.get('Modem', 'fmin', '-50.0')) ModemParam.tuning_range_fmin = float(conf.get('Modem', 'fmin', '-50.0'))
ModemParam.tuning_range_fmax = float(conf.get('Modem', 'fmax', '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') Modem.respond_to_cq = conf.get('Modem', 'qrv', 'True')
ARQ.rx_buffer_size = int(conf.get('Modem', 'rx_buffer_size', '16')) ARQ.rx_buffer_size = int(conf.get('Modem', 'rx_buffer_size', '16'))
Modem.enable_explorer = conf.get('Modem', 'explorer', 'False') Modem.enable_explorer = conf.get('Modem', 'explorer', 'False')
@ -368,6 +386,8 @@ if __name__ == "__main__":
TCIParam.port = int(conf.get('TCI', 'tci_port', '50001')) TCIParam.port = int(conf.get('TCI', 'tci_port', '50001'))
ModemParam.tx_delay = int(conf.get('Modem', 'tx_delay', '0')) ModemParam.tx_delay = int(conf.get('Modem', 'tx_delay', '0'))
MeshParam.enable_protocol = conf.get('MESH','mesh_enable','False') MeshParam.enable_protocol = conf.get('MESH','mesh_enable','False')
MeshParam.transmit_morse_identifier = conf.get('Modem','transmit_morse_identifier','False')
except KeyError as e: except KeyError as e:
log.warning("[CFG] Error reading config file near", key=str(e)) log.warning("[CFG] Error reading config file near", key=str(e))
except Exception as e: except Exception as e:

View file

@ -16,20 +16,17 @@ import sys
import threading import threading
import time import time
from collections import deque from collections import deque
import wave
import codec2 import codec2
import itertools import itertools
import numpy as np import numpy as np
import sock import sock
import sounddevice as sd import sounddevice as sd
import static
from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem
from static import FRAME_TYPE from static import FRAME_TYPE
import structlog import structlog
import ujson as json import ujson as json
import tci 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, \ from queues import DATA_QUEUE_RECEIVED, MODEM_RECEIVED_QUEUE, MODEM_TRANSMIT_QUEUE, RIGCTLD_COMMAND_QUEUE, \
AUDIO_RECEIVED_QUEUE, AUDIO_TRANSMIT_QUEUE, MESH_RECEIVED_QUEUE AUDIO_RECEIVED_QUEUE, AUDIO_TRANSMIT_QUEUE, MESH_RECEIVED_QUEUE
@ -80,6 +77,8 @@ class RF:
self.AUDIO_CHANNELS = 1 self.AUDIO_CHANNELS = 1
self.MODE = 0 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 # 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/127
# https://github.com/DJ2LS/FreeDATA/issues/99 # https://github.com/DJ2LS/FreeDATA/issues/99
@ -460,6 +459,7 @@ class RF:
# self.log.debug("[MDM] callback") # self.log.debug("[MDM] callback")
x = np.frombuffer(data_in48k, dtype=np.int16) x = np.frombuffer(data_in48k, dtype=np.int16)
x = self.resampler.resample48_to_8(x) x = self.resampler.resample48_to_8(x)
x = set_audio_volume(x, AudioParam.rx_audio_level)
# audio recording for debugging purposes # audio recording for debugging purposes
if AudioParam.audio_record: if AudioParam.audio_record:
@ -745,18 +745,7 @@ class RF:
) )
start_of_transmission = time.time() start_of_transmission = time.time()
txbuffer = cw.MorseCodePlayer().text_to_signal("DJ2LS-1") txbuffer_out = 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
self.mod_out_locked = True self.mod_out_locked = True
self.enqueue_modulation(txbuffer_out) self.enqueue_modulation(txbuffer_out)
@ -808,6 +797,8 @@ class RF:
self.log.debug("[MDM] ON AIR TIME", time=transmission_time) self.log.debug("[MDM] ON AIR TIME", time=transmission_time)
def enqueue_modulation(self, txbuffer_out): def enqueue_modulation(self, txbuffer_out):
chunk_length = self.AUDIO_FRAMES_PER_BUFFER_TX # 4800 chunk_length = self.AUDIO_FRAMES_PER_BUFFER_TX # 4800
chunk = [ chunk = [
txbuffer_out[i: i + chunk_length] txbuffer_out[i: i + chunk_length]
@ -822,7 +813,6 @@ class RF:
# self.log.debug("[MDM] mod out shorter than audio buffer", delta=delta) # self.log.debug("[MDM] mod out shorter than audio buffer", delta=delta)
self.modoutqueue.append(c) self.modoutqueue.append(c)
def demodulate_audio( def demodulate_audio(
self, self,
audiobuffer: codec2.audio_buffer, audiobuffer: codec2.audio_buffer,
@ -854,6 +844,7 @@ class RF:
:return: NIN from freedv instance :return: NIN from freedv instance
:rtype: int :rtype: int
""" """
nbytes = 0 nbytes = 0
try: try:
while self.stream.active: while self.stream.active:
@ -871,14 +862,15 @@ class RF:
# 10 error decoding == NACK # 10 error decoding == NACK
rx_status = codec2.api.freedv_get_rx_status(freedv) 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 # we need to disable this if in testmode as its causing problems with FIFO it seems
if not TESTMODE: if not TESTMODE:
ModemParam.is_codec2_traffic = True ModemParam.is_codec2_traffic = True
self.is_codec2_traffic_counter = self.is_codec2_traffic_cooldown
if not ModemParam.channel_busy: if not ModemParam.channel_busy:
self.log.debug("[MDM] Setting channel_busy since codec2 data detected") self.log.debug("[MDM] Setting channel_busy since codec2 data detected")
ModemParam.channel_busy=True ModemParam.channel_busy=True
ModemParam.channel_busy_delay+=10 ModemParam.channel_busy_delay += 10
self.log.debug( self.log.debug(
"[MDM] [demod_audio] modem state", mode=mode_name, rx_status=rx_status, "[MDM] [demod_audio] modem state", mode=mode_name, rx_status=rx_status,
sync_flag=codec2.api.rx_sync_flags_to_text[rx_status] sync_flag=codec2.api.rx_sync_flags_to_text[rx_status]
@ -886,6 +878,13 @@ class RF:
else: else:
ModemParam.is_codec2_traffic = False ModemParam.is_codec2_traffic = False
# decrement codec traffic counter for making state smoother
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: if rx_status == 10:
state_buffer.append(rx_status) state_buffer.append(rx_status)
@ -897,7 +896,6 @@ class RF:
# process commands only if Modem.listen = True # process commands only if Modem.listen = True
if Modem.listen: if Modem.listen:
# ignore data channel opener frames for avoiding toggle states # ignore data channel opener frames for avoiding toggle states
# use case: opener already received, but ack got lost and we are receiving # use case: opener already received, but ack got lost and we are receiving
# an opener again # an opener again
@ -1139,7 +1137,7 @@ class RF:
def get_scatter(self, freedv: ctypes.c_void_p) -> None: def get_scatter(self, freedv: ctypes.c_void_p) -> None:
""" """
Ask codec2 for data about the received signal and calculate the scatter plot. 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 :param freedv: codec2 instance to query
:type freedv: ctypes.c_void_p :type freedv: ctypes.c_void_p
@ -1181,7 +1179,7 @@ class RF:
""" """
Ask codec2 for data about the received signal and calculate Ask codec2 for data about the received signal and calculate
the signal-to-noise ratio. the signal-to-noise ratio.
Side-effect: sets ModemParam.snr Side effect: sets ModemParam.snr
:param freedv: codec2 instance to query :param freedv: codec2 instance to query
:type freedv: ctypes.c_void_p :type freedv: ctypes.c_void_p
@ -1227,7 +1225,7 @@ class RF:
def update_rig_data(self) -> None: def update_rig_data(self) -> None:
""" """
Request information about the current state of the radio via hamlib Request information about the current state of the radio via hamlib
Side-effect: sets Side effect: sets
- HamlibParam.hamlib_frequency - HamlibParam.hamlib_frequency
- HamlibParam.hamlib_mode - HamlibParam.hamlib_mode
- HamlibParam.hamlib_bandwidth - HamlibParam.hamlib_bandwidth
@ -1258,6 +1256,7 @@ class RF:
e=e, e=e,
) )
threading.Event().wait(1) threading.Event().wait(1)
def calculate_fft(self) -> None: def calculate_fft(self) -> None:
""" """
Calculate an average signal strength of the channel to assess Calculate an average signal strength of the channel to assess
@ -1366,7 +1365,7 @@ class RF:
ModemParam.channel_busy_slot[slot] = False ModemParam.channel_busy_slot[slot] = False
# increment slot # increment slot
slot += 1 slot += 1
if (addDelay): if addDelay:
# Limit delay counter to a maximum of 200. The higher this value, # Limit delay counter to a maximum of 200. The higher this value,
# the longer we will wait until releasing state # the longer we will wait until releasing state
ModemParam.channel_busy = True ModemParam.channel_busy = True
@ -1460,31 +1459,39 @@ def get_bytes_per_frame(mode: int) -> int:
# get number of bytes per frame for mode # get number of bytes per frame for mode
return int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8) return int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
def set_audio_volume(datalist: np.ndarray, dB: float) -> np.ndarray:
def set_audio_volume(datalist, volume: float) -> np.int16:
""" """
Scale values for the provided audio samples by volume, Scale values for the provided audio samples by dB.
`volume` is clipped to the range of 0-200
:param datalist: Audio samples to scale :param datalist: Audio samples to scale
:type datalist: NDArray[np.int16] :type datalist: np.ndarray
:param volume: "Percentage" (0-200) to scale samples :param dB: Decibels to scale samples, constrained to the range [-50, 50]
:type volume: float :type dB: float
:return: Scaled audio samples :return: Scaled audio samples
:rtype: np.int16 :rtype: np.ndarray
""" """
# make sure we have float as data type to avoid crash
try: try:
volume = float(volume) dB = float(dB)
except Exception as e: except ValueError as e:
print(f"[MDM] changing audio volume failed with error: {e}") print(f"[MDM] Changing audio volume failed with error: {e}")
volume = 100.0 dB = 0.0 # 0 dB means no change
# Clip volume provided to acceptable values # Clip dB value to the range [-50, 50]
volume = np.clip(volume, 0, 200) # limit to max value of 255 dB = np.clip(dB, -30, 20)
# Scale samples by the ratio of volume / 100.0
data = np.fromstring(datalist, np.int16) * (volume / 100.0) # type: ignore # Ensure datalist is an np.ndarray
return data.astype(np.int16) 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(): def get_modem_error_state():

View file

@ -256,6 +256,12 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
else: else:
self.modem_set_tx_audio_level(received_json) 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 # TRANSMIT TEST FRAME
if received_json["type"] == "set" and received_json["command"] == "send_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, 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): def modem_set_send_test_frame(self, received_json):
try: try:
DATA_QUEUE_TRANSMIT.put(["SEND_TEST_FRAME"]) DATA_QUEUE_TRANSMIT.put(["SEND_TEST_FRAME"])
@ -1078,7 +1096,8 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
low_bandwidth_mode = str(helpers.return_key_from_object("False", startparam, "low_bandwidth_mode")) 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_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")) 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")) 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")) 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")) enable_explorer = str(helpers.return_key_from_object("False", startparam, "enable_explorer"))
@ -1131,7 +1150,8 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
tx_delay, tx_delay,
tci_ip, tci_ip,
tci_port, tci_port,
enable_mesh enable_mesh,
rx_audio_level,
] ]
) )
command_response("start_modem", True) command_response("start_modem", True)
@ -1345,7 +1365,8 @@ def send_modem_state():
"rf_level": str(HamlibParam.hamlib_rf), "rf_level": str(HamlibParam.hamlib_rf),
"strength": str(HamlibParam.hamlib_strength), "strength": str(HamlibParam.hamlib_strength),
"alc": str(HamlibParam.alc), "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), "audio_auto_tune": str(AudioParam.audio_auto_tune),
"speed_level": str(ARQ.arq_speed_level), "speed_level": str(ARQ.arq_speed_level),
"mode": str(HamlibParam.hamlib_mode), "mode": str(HamlibParam.hamlib_mode),

View file

@ -11,6 +11,7 @@ from dataclasses import dataclass, field
from typing import List from typing import List
import subprocess import subprocess
from enum import Enum from enum import Enum
import threading
# CHANNEL_STATE = 'RECEIVING_SIGNALLING' # CHANNEL_STATE = 'RECEIVING_SIGNALLING'
@ -33,6 +34,7 @@ class ARQ:
arq_session_state: str = "disconnected" # can be: disconnected, disconnecting, connected, connecting, failed arq_session_state: str = "disconnected" # can be: disconnected, disconnecting, connected, connecting, failed
arq_session: bool = False arq_session: bool = False
arq_state: bool = False arq_state: bool = False
arq_state_event: threading.Event = field(default_factory=threading.Event)
# ARQ PROTOCOL VERSION # ARQ PROTOCOL VERSION
# v.5 - signalling frame uses datac0 # v.5 - signalling frame uses datac0
# v.6 - signalling frame uses datac13 # v.6 - signalling frame uses datac13
@ -48,7 +50,8 @@ class ARQ:
@dataclass @dataclass
class AudioParam: class AudioParam:
tx_audio_level: int = 50 tx_audio_level: int = 0
rx_audio_level: int = 0
audio_input_devices = [] audio_input_devices = []
audio_output_devices = [] audio_output_devices = []
audio_input_device: int = -2 audio_input_device: int = -2
@ -114,10 +117,10 @@ class ModemParam:
@dataclass @dataclass
class Station: class Station:
mycallsign: bytes = b"AA0AA" mycallsign: bytes = b"AA0AA-0"
mycallsign_crc: bytes = b"A" mycallsign_crc: bytes = b"A"
dxcallsign: bytes = b"ZZ9YY" dxcallsign: bytes = b"ZZ9YY-0"
dxcallsign_crc: bytes = b"A" dxcallsign_crc: bytes = b"B"
mygrid: bytes = b"" mygrid: bytes = b""
dxgrid: bytes = b"" dxgrid: bytes = b""
ssid_list = [] # ssid list we are responding to ssid_list = [] # ssid list we are responding to
@ -134,7 +137,7 @@ class TCIParam:
@dataclass @dataclass
class Modem: class Modem:
version = "0.11.1-alpha.3" version = "0.11.2-alpha.4"
host: str = "0.0.0.0" host: str = "0.0.0.0"
port: int = 3000 port: int = 3000
SOCKET_TIMEOUT: int = 1 # seconds SOCKET_TIMEOUT: int = 1 # seconds
@ -149,6 +152,7 @@ class Modem:
heard_stations = [] heard_stations = []
listen: bool = True listen: bool = True
enable_hmac: bool = True enable_hmac: bool = True
transmit_morse_identifier: bool = False
# ------------ # ------------