mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
Merge branch 'develop' into qm-develop-gui
This commit is contained in:
commit
22e92af2ac
14 changed files with 406 additions and 309 deletions
|
@ -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
|
||||
</button>
|
||||
</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">
|
||||
<span class="input-group-text">TX Level</span>
|
||||
<span class="input-group-text">{{ settings.tx_audio_level }}</span>
|
||||
|
@ -1201,11 +1221,11 @@ const transmissionSpeedChartDataMessageInfo = computed(() => ({
|
|||
<input
|
||||
type="range"
|
||||
class="form-range"
|
||||
min="0"
|
||||
max="250"
|
||||
min="-30"
|
||||
max="20"
|
||||
step="1"
|
||||
id="audioLevelTX"
|
||||
@click="set_audio_level()"
|
||||
@click="set_tx_audio_level()"
|
||||
v-model="settings.tx_audio_level"
|
||||
/></span>
|
||||
</div>
|
||||
|
|
|
@ -195,6 +195,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,
|
||||
|
|
|
@ -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",\
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -171,7 +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.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))
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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("");
|
||||
|
@ -115,6 +117,7 @@ export const useStateStore = defineStore("stateStore", () => {
|
|||
audio_recording,
|
||||
hamlib_status,
|
||||
tx_audio_level,
|
||||
rx_audio_level,
|
||||
alc,
|
||||
updateTncState,
|
||||
arq_transmission_percent,
|
||||
|
|
|
@ -58,6 +58,7 @@ class CONFIG:
|
|||
'rx': data[3],
|
||||
'tx': data[4],
|
||||
'txaudiolevel': data[14],
|
||||
'rxaudiolevel': data[25],
|
||||
'auto_tune': data[19]
|
||||
|
||||
}
|
||||
|
@ -77,14 +78,15 @@ class CONFIG:
|
|||
'explorer': data[17],
|
||||
'stats': data[19],
|
||||
'fsk': data[13],
|
||||
'tx_delay': data[21]
|
||||
}
|
||||
'tx_delay': data[21],
|
||||
'transmit_morse_identifier' : data[26]
|
||||
}
|
||||
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]
|
||||
}
|
||||
|
||||
|
|
42
modem/cw.py
42
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
|
||||
|
||||
|
|
|
@ -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,10 +489,19 @@ class DAEMON:
|
|||
options.append(data[21])
|
||||
|
||||
#Mesh
|
||||
print(data[24])
|
||||
if data[24] == "True":
|
||||
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
|
||||
config.write_entire_config(data)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
@ -110,7 +116,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
|
||||
|
@ -124,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
|
||||
|
||||
|
@ -140,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
|
||||
|
@ -182,6 +187,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 = 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
|
||||
# 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[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)
|
||||
|
@ -562,16 +568,11 @@ 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
|
||||
# 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:
|
||||
|
@ -607,22 +608,20 @@ 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
|
||||
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
|
||||
self.frame_nack_counter += 1
|
||||
|
||||
|
||||
# we need to clear our rx burst buffer
|
||||
ARQ.rx_burst_buffer = []
|
||||
|
||||
|
@ -631,16 +630,14 @@ 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")
|
||||
|
||||
|
||||
# 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)
|
||||
|
@ -654,16 +651,10 @@ 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 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)
|
||||
|
||||
|
@ -815,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)
|
||||
|
@ -1008,9 +1003,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
|
||||
|
@ -1041,6 +1034,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):
|
||||
"""
|
||||
|
||||
|
@ -1742,8 +1742,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")
|
||||
|
@ -1753,35 +1751,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()
|
||||
|
||||
|
@ -1995,6 +1967,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):
|
||||
|
@ -2162,114 +2138,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 +2178,93 @@ 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() 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):
|
||||
"""
|
||||
|
@ -2338,7 +2296,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
|
||||
|
@ -2356,9 +2314,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
|
||||
|
@ -2406,29 +2361,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])
|
||||
|
@ -2457,6 +2397,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
|
||||
|
||||
|
@ -2505,7 +2446,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
|
||||
|
||||
|
@ -2539,10 +2482,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,
|
||||
|
@ -2562,13 +2505,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",
|
||||
|
@ -2578,7 +2523,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,
|
||||
|
@ -2761,8 +2705,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",
|
||||
|
@ -2778,7 +2720,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()
|
||||
|
@ -2849,6 +2790,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 (
|
||||
|
@ -2926,9 +2869,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"])
|
||||
|
||||
|
||||
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
|
||||
def calculate_transfer_rate_rx(
|
||||
self, rx_start_of_transmission: float, receivedbytes: int
|
||||
|
@ -3263,6 +3224,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
|
||||
|
@ -3375,10 +3337,38 @@ 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
|
||||
)
|
||||
|
||||
|
||||
# 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 <= time.time() or modem_error_state:
|
||||
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 or override:
|
||||
self.log.warning(
|
||||
"[Modem] Burst decoding error or timeout",
|
||||
attempt=self.n_retries_per_burst,
|
||||
|
@ -3389,7 +3379,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
|
||||
|
@ -3398,8 +3388,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
|
||||
|
@ -3420,13 +3410,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
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
@ -261,6 +268,15 @@ if __name__ == "__main__":
|
|||
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()
|
||||
|
||||
# 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_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
|
||||
|
@ -316,7 +333,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)
|
||||
|
@ -358,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')
|
||||
|
@ -368,6 +386,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:
|
||||
|
|
|
@ -16,20 +16,17 @@ 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
|
||||
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
|
||||
|
||||
|
@ -80,6 +77,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
|
||||
|
@ -460,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:
|
||||
|
@ -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]
|
||||
|
@ -822,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,
|
||||
|
@ -854,6 +844,7 @@ class RF:
|
|||
:return: NIN from freedv instance
|
||||
:rtype: int
|
||||
"""
|
||||
|
||||
nbytes = 0
|
||||
try:
|
||||
while self.stream.active:
|
||||
|
@ -871,14 +862,15 @@ 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
|
||||
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]
|
||||
|
@ -886,6 +878,13 @@ class RF:
|
|||
else:
|
||||
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:
|
||||
state_buffer.append(rx_status)
|
||||
|
||||
|
@ -897,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
|
||||
|
@ -1139,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
|
||||
|
@ -1181,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
|
||||
|
@ -1227,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
|
||||
|
@ -1258,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
|
||||
|
@ -1366,7 +1365,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
|
||||
|
@ -1460,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():
|
||||
|
|
|
@ -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"])
|
||||
|
@ -1078,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"))
|
||||
|
@ -1131,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)
|
||||
|
@ -1345,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),
|
||||
|
|
|
@ -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
|
||||
|
@ -48,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
|
||||
|
@ -114,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
|
||||
|
@ -134,7 +137,7 @@ class TCIParam:
|
|||
|
||||
@dataclass
|
||||
class Modem:
|
||||
version = "0.11.1-alpha.3"
|
||||
version = "0.11.2-alpha.4"
|
||||
host: str = "0.0.0.0"
|
||||
port: int = 3000
|
||||
SOCKET_TIMEOUT: int = 1 # seconds
|
||||
|
@ -149,6 +152,7 @@ class Modem:
|
|||
heard_stations = []
|
||||
listen: bool = True
|
||||
enable_hmac: bool = True
|
||||
transmit_morse_identifier: bool = False
|
||||
|
||||
# ------------
|
||||
|
||||
|
|
Loading…
Reference in a new issue