Merge branch 'develop' of github.com:DJ2LS/FreeDATA into develop

This commit is contained in:
Mashintime 2023-12-16 09:14:54 -05:00
commit 85689a4d37
14 changed files with 337 additions and 613 deletions

View file

@ -56,9 +56,6 @@ class ARQSessionIRS(arq_session.ARQSession):
self.transmitted_acks = 0 self.transmitted_acks = 0
def set_modem_decode_modes(self, modes):
pass
def all_data_received(self): def all_data_received(self):
return self.received_bytes == len(self.received_data) return self.received_bytes == len(self.received_data)
@ -90,7 +87,7 @@ class ARQSessionIRS(arq_session.ARQSession):
self.dxcall, self.dxcall,
self.version, self.version,
self.snr[0]) self.snr[0])
self.launch_transmit_and_wait(ack_frame, self.TIMEOUT_CONNECT, mode=FREEDV_MODE.datac13) self.launch_transmit_and_wait(ack_frame, self.TIMEOUT_CONNECT, mode=FREEDV_MODE.signalling)
self.set_state(self.STATE_OPEN_ACK_SENT) self.set_state(self.STATE_OPEN_ACK_SENT)
def send_info_ack(self, info_frame): def send_info_ack(self, info_frame):
@ -100,23 +97,33 @@ class ARQSessionIRS(arq_session.ARQSession):
self.dx_snr.append(info_frame['snr']) self.dx_snr.append(info_frame['snr'])
self.calibrate_speed_settings() self.calibrate_speed_settings()
self.set_modem_listening_modes(self.speed_level) self.set_modem_decode_modes(self.speed_level)
info_ack = self.frame_factory.build_arq_session_info_ack( info_ack = self.frame_factory.build_arq_session_info_ack(
self.id, self.total_crc, self.snr[0], self.id, self.total_crc, self.snr[0],
self.speed_level, self.frames_per_burst) self.speed_level, self.frames_per_burst)
self.launch_transmit_and_wait(info_ack, self.TIMEOUT_CONNECT, mode=FREEDV_MODE.datac13) self.launch_transmit_and_wait(info_ack, self.TIMEOUT_CONNECT, mode=FREEDV_MODE.signalling)
self.set_state(self.STATE_INFO_ACK_SENT) self.set_state(self.STATE_INFO_ACK_SENT)
def send_burst_nack(self): def send_burst_nack(self):
self.calibrate_speed_settings() self.calibrate_speed_settings()
self.set_modem_listening_modes(self.speed_level) self.set_modem_decode_modes(self.speed_level)
nack = self.frame_factory.build_arq_burst_ack(self.id, self.received_bytes, self.speed_level, self.frames_per_burst, self.snr[0]) nack = self.frame_factory.build_arq_burst_ack(self.id, self.received_bytes, self.speed_level, self.frames_per_burst, self.snr[0])
self.transmit_and_wait(nack) self.transmit_and_wait(nack)
def set_modem_listening_modes(self, speed_level): def set_modem_decode_modes(self, speed_level):
# TODO
# We want to set the modems listening modes somehow... for mode in self.modem.demodulator.MODE_DICT:
self.modem.demodulator.MODE_DICT[mode]["decode"] = False
# signalling is always true
self.modem.demodulator.MODE_DICT[FREEDV_MODE.signalling.value]["decode"] = True
mode = self.get_mode_by_speed_level(self.speed_level)
# Enable mode based on speed_level
self.modem.demodulator.MODE_DICT[mode.value]["decode"] = True
self.log(f"Modem set to speed level {speed_level}")
return return

View file

@ -71,7 +71,7 @@ class ARQSessionISS(arq_session.ARQSession):
def start(self): def start(self):
session_open_frame = self.frame_factory.build_arq_session_open(self.dxcall, self.id) session_open_frame = self.frame_factory.build_arq_session_open(self.dxcall, self.id)
self.launch_twr(session_open_frame, self.TIMEOUT_CONNECT_ACK, self.RETRIES_CONNECT, mode=FREEDV_MODE.datac13) self.launch_twr(session_open_frame, self.TIMEOUT_CONNECT_ACK, self.RETRIES_CONNECT, mode=FREEDV_MODE.signalling)
self.set_state(self.STATE_OPEN_SENT) self.set_state(self.STATE_OPEN_SENT)
def set_speed_and_frames_per_burst(self, frame): def set_speed_and_frames_per_burst(self, frame):
@ -84,7 +84,7 @@ class ARQSessionISS(arq_session.ARQSession):
info_frame = self.frame_factory.build_arq_session_info(self.id, len(self.data), info_frame = self.frame_factory.build_arq_session_info(self.id, len(self.data),
helpers.get_crc_32(self.data), helpers.get_crc_32(self.data),
self.snr[0]) self.snr[0])
self.launch_twr(info_frame, self.TIMEOUT_CONNECT_ACK, self.RETRIES_CONNECT, mode=FREEDV_MODE.datac13) self.launch_twr(info_frame, self.TIMEOUT_CONNECT_ACK, self.RETRIES_CONNECT, mode=FREEDV_MODE.signalling)
self.set_state(self.STATE_INFO_SENT) self.set_state(self.STATE_INFO_SENT)
def send_data(self, irs_frame): def send_data(self, irs_frame):

View file

@ -7,6 +7,7 @@ import crcengine
import sounddevice as sd import sounddevice as sd
import structlog import structlog
import numpy as np import numpy as np
import queue
atexit.register(sd._terminate) atexit.register(sd._terminate)
@ -206,4 +207,125 @@ def set_audio_volume(datalist: np.ndarray, dB: float) -> np.ndarray:
scaled_data = datalist * scale_factor scaled_data = datalist * scale_factor
# Clip values to int16 range and convert data type # Clip values to int16 range and convert data type
return np.clip(scaled_data, -32768, 32767).astype(np.int16) return np.clip(scaled_data, -32768, 32767).astype(np.int16)
RMS_COUNTER = 0
CHANNEL_BUSY_DELAY = 0
def calculate_fft(data, fft_queue, states) -> None:
"""
Calculate an average signal strength of the channel to assess
whether the channel is "busy."
"""
# Initialize dbfs counter
# rms_counter = 0
# https://gist.github.com/ZWMiller/53232427efc5088007cab6feee7c6e4c
# Fast Fourier Transform, 10*log10(abs) is to scale it to dB
# and make sure it's not imaginary
global RMS_COUNTER, CHANNEL_BUSY_DELAY
try:
fftarray = np.fft.rfft(data)
# Set value 0 to 1 to avoid division by zero
fftarray[fftarray == 0] = 1
dfft = 10.0 * np.log10(abs(fftarray))
# get average of dfft
avg = np.mean(dfft)
# Detect signals which are higher than the
# average + 10 (+10 smoothes the output).
# Data higher than the average must be a signal.
# Therefore we are setting it to 100 so it will be highlighted
# Have to do this when we are not transmitting so our
# own sending data will not affect this too much
if not states.isTransmitting():
dfft[dfft > avg + 15] = 100
# Calculate audio dbfs
# https://stackoverflow.com/a/9763652
# calculate dbfs every 50 cycles for reducing CPU load
RMS_COUNTER += 1
if RMS_COUNTER > 5:
d = np.frombuffer(data, np.int16).astype(np.float32)
# calculate RMS and then dBFS
# https://dsp.stackexchange.com/questions/8785/how-to-compute-dbfs
# try except for avoiding runtime errors by division/0
try:
rms = int(np.sqrt(np.max(d ** 2)))
if rms == 0:
raise ZeroDivisionError
audio_dbfs = 20 * np.log10(rms / 32768)
states.set("audio_dbfs", audio_dbfs)
except Exception as e:
states.set("audio_dbfs", -100)
RMS_COUNTER = 0
# Convert data to int to decrease size
dfft = dfft.astype(int)
# Create list of dfft
dfftlist = dfft.tolist()
# Reduce area where the busy detection is enabled
# We want to have this in correlation with mode bandwidth
# TODO This is not correctly and needs to be checked for correct maths
# dfftlist[0:1] = 10,15Hz
# Bandwidth[Hz] / 10,15
# narrowband = 563Hz = 56
# wideband = 1700Hz = 167
# 1500Hz = 148
# 2700Hz = 266
# 3200Hz = 315
# slot
slot = 0
slot1 = [0, 65]
slot2 = [65,120]
slot3 = [120, 176]
slot4 = [176, 231]
slot5 = [231, len(dfftlist)]
slotbusy = [False,False,False,False,False]
# Set to true if we should increment delay count; else false to decrement
addDelay=False
for range in [slot1, slot2, slot3, slot4, slot5]:
range_start = range[0]
range_end = range[1]
# define the area, we are detecting busy state
slotdfft = dfft[range_start:range_end]
# Check for signals higher than average by checking for "100"
# If we have a signal, increment our channel_busy delay counter
# so we have a smoother state toggle
if np.sum(slotdfft[slotdfft > avg + 15]) >= 200 and not states.isTransmitting():
addDelay=True
slotbusy[slot]=True
#states.channel_busy_slot[slot] = True
# increment slot
slot += 1
states.set_channel_slot_busy(slotbusy)
if addDelay:
# Limit delay counter to a maximum of 200. The higher this value,
# the longer we will wait until releasing state
states.set("channel_busy", True)
CHANNEL_BUSY_DELAY = min(CHANNEL_BUSY_DELAY + 10, 200)
else:
# Decrement channel busy counter if no signal has been detected.
CHANNEL_BUSY_DELAY = max(CHANNEL_BUSY_DELAY - 1, 0)
# When our channel busy counter reaches 0, toggle state to False
if CHANNEL_BUSY_DELAY == 0:
states.set("channel_busy", False)
# erase queue if greater than 10
if fft_queue.qsize() >= 10:
fft_queue = queue.Queue()
fft_queue.put(dfftlist[:315]) # 315 --> bandwidth 3200
except Exception as err:
print(f"[MDM] calculate_fft: Exception: {err}")
print("[MDM] Setting fft=0")
fft_queue.put([0])

View file

@ -24,16 +24,12 @@ class FREEDV_MODE(Enum):
""" """
Enumeration for codec2 modes and names Enumeration for codec2 modes and names
""" """
sig0 = 19 signalling = 19
sig1 = 19
datac0 = 14 datac0 = 14
datac1 = 10 datac1 = 10
datac3 = 12 datac3 = 12
datac4 = 18 datac4 = 18
datac13 = 19 datac13 = 19
fsk_ldpc = 9
fsk_ldpc_0 = 200
fsk_ldpc_1 = 201
class FREEDV_MODE_USED_SLOTS(Enum): class FREEDV_MODE_USED_SLOTS(Enum):
@ -435,24 +431,24 @@ def open_instance(mode: int) -> ctypes.c_void_p:
:return: C-function of the requested codec2 instance :return: C-function of the requested codec2 instance
:rtype: ctypes.c_void_p :rtype: ctypes.c_void_p
""" """
if mode in [FREEDV_MODE.fsk_ldpc_0.value]: # if mode in [FREEDV_MODE.fsk_ldpc_0.value]:
return ctypes.cast( # return ctypes.cast(
api.freedv_open_advanced( # api.freedv_open_advanced(
FREEDV_MODE.fsk_ldpc.value, # FREEDV_MODE.fsk_ldpc.value,
ctypes.byref(api.FREEDV_MODE_FSK_LDPC_0_ADV), # ctypes.byref(api.FREEDV_MODE_FSK_LDPC_0_ADV),
), # ),
ctypes.c_void_p, # ctypes.c_void_p,
) # )
#
if mode in [FREEDV_MODE.fsk_ldpc_1.value]: # if mode in [FREEDV_MODE.fsk_ldpc_1.value]:
return ctypes.cast( # return ctypes.cast(
api.freedv_open_advanced( # api.freedv_open_advanced(
FREEDV_MODE.fsk_ldpc.value, # FREEDV_MODE.fsk_ldpc.value,
ctypes.byref(api.FREEDV_MODE_FSK_LDPC_1_ADV), # ctypes.byref(api.FREEDV_MODE_FSK_LDPC_1_ADV),
), # ),
ctypes.c_void_p, # ctypes.c_void_p,
) # )
#
return ctypes.cast(api.freedv_open(mode), ctypes.c_void_p) return ctypes.cast(api.freedv_open(mode), ctypes.c_void_p)
def get_bytes_per_frame(mode: int) -> int: def get_bytes_per_frame(mode: int) -> int:

View file

@ -30,11 +30,7 @@ class TxCommand():
pass pass
def get_tx_mode(self): def get_tx_mode(self):
return ( return FREEDV_MODE.signalling
FREEDV_MODE.fsk_ldpc_0.value
if self.config['MODEM']['enable_fsk']
else FREEDV_MODE.sig0.value
)
def make_modem_queue_item(self, mode, repeat, repeat_delay, frame): def make_modem_queue_item(self, mode, repeat, repeat_delay, frame):
return { return {
@ -52,3 +48,9 @@ class TxCommand():
self.emit_event(event_queue) self.emit_event(event_queue)
self.logger.info(self.log_message()) self.logger.info(self.log_message())
self.transmit(modem) self.transmit(modem)
def test(self, event_queue: queue.Queue):
self.emit_event(event_queue)
self.logger.info(self.log_message())
frame = self.build_frame()
return frame

View file

@ -12,27 +12,33 @@ TESTMODE = False
class Demodulator(): class Demodulator():
def __init__(self, config, audio_rx_q, modem_rx_q, data_q_rx, states, event_manager): MODE_DICT = {}
# Iterate over the FREEDV_MODE enum members
for mode in codec2.FREEDV_MODE:
MODE_DICT[mode.value] = {
'decode': False,
'bytes_per_frame': None,
'bytes_out': None,
'audio_buffer': None,
'nin': None,
'instance': None,
'state_buffer': [],
'name': mode.name.upper(),
'decoding_thread': None
}
def __init__(self, config, audio_rx_q, modem_rx_q, data_q_rx, states, event_manager, fft_queue):
self.log = structlog.get_logger("Demodulator")
self.tuning_range_fmin = config['MODEM']['tuning_range_fmin'] self.tuning_range_fmin = config['MODEM']['tuning_range_fmin']
self.tuning_range_fmax = config['MODEM']['tuning_range_fmax'] self.tuning_range_fmax = config['MODEM']['tuning_range_fmax']
self.enable_fsk = config['MODEM']['enable_fsk']
self.rx_audio_level = config['AUDIO']['rx_audio_level'] self.rx_audio_level = config['AUDIO']['rx_audio_level']
self.AUDIO_FRAMES_PER_BUFFER_RX = 2400 * 2 # 8192 self.AUDIO_FRAMES_PER_BUFFER_RX = 4800
self.buffer_overflow_counter = [0, 0, 0, 0, 0, 0, 0, 0] self.buffer_overflow_counter = [0, 0, 0, 0, 0, 0, 0, 0]
self.is_codec2_traffic_counter = 0 self.is_codec2_traffic_counter = 0
self.is_codec2_traffic_cooldown = 20 self.is_codec2_traffic_cooldown = 20
# Receive only specific modes to reduce CPU load
self.RECEIVE_SIG0 = True
self.RECEIVE_SIG1 = False
self.RECEIVE_DATAC1 = False
self.RECEIVE_DATAC3 = False
self.RECEIVE_DATAC4 = False
self.RXCHANNEL = ""
self.log = structlog.get_logger("Demodulator")
self.audio_received_queue = audio_rx_q self.audio_received_queue = audio_rx_q
self.modem_received_queue = modem_rx_q self.modem_received_queue = modem_rx_q
@ -41,118 +47,32 @@ class Demodulator():
self.states = states self.states = states
self.event_manager = event_manager self.event_manager = event_manager
self.fft_queue = fft_queue
# init codec2 resampler # init codec2 resampler
self.resampler = codec2.resampler() self.resampler = codec2.resampler()
self.init_state_buffers()
self.init_codec2() self.init_codec2()
def init_state_buffers(self): # enable decoding of signalling modes
# state buffer self.MODE_DICT[codec2.FREEDV_MODE.signalling.value]["decode"] = True
self.SIG0_DATAC13_STATE = []
self.SIG1_DATAC13_STATE = []
self.DAT0_DATAC1_STATE = []
self.DAT0_DATAC3_STATE = []
self.DAT0_DATAC4_STATE = []
self.FSK_LDPC0_STATE = []
self.FSK_LDPC1_STATE = []
def init_codec2(self): def init_codec2(self):
# Open codec2 instances # Open codec2 instances
for mode in codec2.FREEDV_MODE:
# DATAC13 self.init_codec2_mode(mode.value)
# SIGNALLING MODE 0 - Used for Connecting - Payload 14 Bytes
self.sig0_datac13_freedv, \
self.sig0_datac13_bytes_per_frame, \
self.sig0_datac13_bytes_out, \
self.sig0_datac13_buffer, \
self.sig0_datac13_nin = \
self.init_codec2_mode(codec2.FREEDV_MODE.datac13.value, None)
# DATAC13
# SIGNALLING MODE 1 - Used for ACK/NACK - Payload 5 Bytes
self.sig1_datac13_freedv, \
self.sig1_datac13_bytes_per_frame, \
self.sig1_datac13_bytes_out, \
self.sig1_datac13_buffer, \
self.sig1_datac13_nin = \
self.init_codec2_mode(codec2.FREEDV_MODE.datac13.value, None)
# DATAC1
self.dat0_datac1_freedv, \
self.dat0_datac1_bytes_per_frame, \
self.dat0_datac1_bytes_out, \
self.dat0_datac1_buffer, \
self.dat0_datac1_nin = \
self.init_codec2_mode(codec2.FREEDV_MODE.datac1.value, None)
# DATAC3
self.dat0_datac3_freedv, \
self.dat0_datac3_bytes_per_frame, \
self.dat0_datac3_bytes_out, \
self.dat0_datac3_buffer, \
self.dat0_datac3_nin = \
self.init_codec2_mode(codec2.FREEDV_MODE.datac3.value, None)
# DATAC4
self.dat0_datac4_freedv, \
self.dat0_datac4_bytes_per_frame, \
self.dat0_datac4_bytes_out, \
self.dat0_datac4_buffer, \
self.dat0_datac4_nin = \
self.init_codec2_mode(codec2.FREEDV_MODE.datac4.value, None)
# FSK LDPC - 0 def init_codec2_mode(self, mode):
self.fsk_ldpc_freedv_0, \
self.fsk_ldpc_bytes_per_frame_0, \
self.fsk_ldpc_bytes_out_0, \
self.fsk_ldpc_buffer_0, \
self.fsk_ldpc_nin_0 = \
self.init_codec2_mode(
codec2.FREEDV_MODE.fsk_ldpc.value,
codec2.api.FREEDV_MODE_FSK_LDPC_0_ADV
)
# FSK LDPC - 1
self.fsk_ldpc_freedv_1, \
self.fsk_ldpc_bytes_per_frame_1, \
self.fsk_ldpc_bytes_out_1, \
self.fsk_ldpc_buffer_1, \
self.fsk_ldpc_nin_1 = \
self.init_codec2_mode(
codec2.FREEDV_MODE.fsk_ldpc.value,
codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV
)
def init_codec2_mode(self, mode, adv):
""" """
Init codec2 and return some important parameters Init codec2 and return some important parameters
Args:
self:
mode:
adv:
Returns:
c2instance, bytes_per_frame, bytes_out, audio_buffer, nin
""" """
if adv:
# FSK Long-distance Parity Code 1 - data frames
c2instance = ctypes.cast(
codec2.api.freedv_open_advanced(
codec2.FREEDV_MODE.fsk_ldpc.value,
ctypes.byref(adv),
),
ctypes.c_void_p,
)
else:
# create codec2 instance # create codec2 instance
c2instance = ctypes.cast( c2instance = ctypes.cast(
codec2.api.freedv_open(mode), ctypes.c_void_p codec2.api.freedv_open(mode), ctypes.c_void_p
) )
# set tuning range # set tuning range
codec2.api.freedv_set_tuning_range( codec2.api.freedv_set_tuning_range(
@ -179,180 +99,60 @@ class Demodulator():
nin = codec2.api.freedv_nin(c2instance) nin = codec2.api.freedv_nin(c2instance)
# Additional Datac0-specific information - these are not referenced anywhere else. # Additional Datac0-specific information - these are not referenced anywhere else.
# self.sig0_datac0_payload_per_frame = self.sig0_datac0_bytes_per_frame - 2 # self.signalling_datac0_payload_per_frame = self.signalling_datac0_bytes_per_frame - 2
# self.sig0_datac0_n_nom_modem_samples = codec2.api.freedv_get_n_nom_modem_samples( # self.signalling_datac0_n_nom_modem_samples = codec2.api.freedv_get_n_nom_modem_samples(
# self.sig0_datac0_freedv # self.signalling_datac0_freedv
# ) # )
# self.sig0_datac0_n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples( # self.signalling_datac0_n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(
# self.sig0_datac0_freedv # self.signalling_datac0_freedv
# ) # )
# self.sig0_datac0_n_tx_preamble_modem_samples = ( # self.signalling_datac0_n_tx_preamble_modem_samples = (
# codec2.api.freedv_get_n_tx_preamble_modem_samples(self.sig0_datac0_freedv) # codec2.api.freedv_get_n_tx_preamble_modem_samples(self.signalling_datac0_freedv)
# ) # )
# self.sig0_datac0_n_tx_postamble_modem_samples = ( # self.signalling_datac0_n_tx_postamble_modem_samples = (
# codec2.api.freedv_get_n_tx_postamble_modem_samples(self.sig0_datac0_freedv) # codec2.api.freedv_get_n_tx_postamble_modem_samples(self.signalling_datac0_freedv)
# ) # )
# return values self.MODE_DICT[mode]["instance"] = c2instance
return c2instance, bytes_per_frame, bytes_out, audio_buffer, nin self.MODE_DICT[mode]["bytes_per_frame"] = bytes_per_frame
self.MODE_DICT[mode]["bytes_out"] = bytes_out
self.MODE_DICT[mode]["audio_buffer"] = audio_buffer
self.MODE_DICT[mode]["nin"] = nin
def start(self, stream): def start(self, stream):
self.stream = stream self.stream = stream
# Start decoder threads for mode in self.MODE_DICT:
if self.enable_fsk: # Start decoder threads
audio_thread_fsk_ldpc0 = threading.Thread( self.MODE_DICT[mode]['decoding_thread'] = threading.Thread(
target=self.audio_fsk_ldpc_0, name="AUDIO_THREAD FSK LDPC0", daemon=True target=self.demodulate_audio,args=[mode], name=self.MODE_DICT[mode]['name'], daemon=True
) )
audio_thread_fsk_ldpc0.start() self.MODE_DICT[mode]['decoding_thread'].start()
audio_thread_fsk_ldpc1 = threading.Thread(
target=self.audio_fsk_ldpc_1, name="AUDIO_THREAD FSK LDPC1", daemon=True
)
audio_thread_fsk_ldpc1.start()
else:
audio_thread_sig0_datac13 = threading.Thread(
target=self.audio_sig0_datac13, name="AUDIO_THREAD DATAC13 - 0", daemon=True
)
audio_thread_sig0_datac13.start()
audio_thread_sig1_datac13 = threading.Thread(
target=self.audio_sig1_datac13, name="AUDIO_THREAD DATAC13 - 1", daemon=True
)
audio_thread_sig1_datac13.start()
audio_thread_dat0_datac1 = threading.Thread(
target=self.audio_dat0_datac1, name="AUDIO_THREAD DATAC1", daemon=True
)
audio_thread_dat0_datac1.start()
audio_thread_dat0_datac3 = threading.Thread(
target=self.audio_dat0_datac3, name="AUDIO_THREAD DATAC3", daemon=True
)
audio_thread_dat0_datac3.start()
audio_thread_dat0_datac4 = threading.Thread(
target=self.audio_dat0_datac4, name="AUDIO_THREAD DATAC4", daemon=True
)
audio_thread_dat0_datac4.start()
def audio_sig0_datac13(self) -> None:
"""Receive data encoded with datac13 - 0"""
self.sig0_datac13_nin = self.demodulate_audio(
self.sig0_datac13_buffer,
self.sig0_datac13_nin,
self.sig0_datac13_freedv,
self.sig0_datac13_bytes_out,
self.sig0_datac13_bytes_per_frame,
self.SIG0_DATAC13_STATE,
"sig0-datac13"
)
def audio_sig1_datac13(self) -> None:
"""Receive data encoded with datac13 - 1"""
self.sig1_datac13_nin = self.demodulate_audio(
self.sig1_datac13_buffer,
self.sig1_datac13_nin,
self.sig1_datac13_freedv,
self.sig1_datac13_bytes_out,
self.sig1_datac13_bytes_per_frame,
self.SIG1_DATAC13_STATE,
"sig1-datac13"
)
def audio_dat0_datac4(self) -> None:
"""Receive data encoded with datac4"""
self.dat0_datac4_nin = self.demodulate_audio(
self.dat0_datac4_buffer,
self.dat0_datac4_nin,
self.dat0_datac4_freedv,
self.dat0_datac4_bytes_out,
self.dat0_datac4_bytes_per_frame,
self.DAT0_DATAC4_STATE,
"dat0-datac4"
)
def audio_dat0_datac1(self) -> None:
"""Receive data encoded with datac1"""
self.dat0_datac1_nin = self.demodulate_audio(
self.dat0_datac1_buffer,
self.dat0_datac1_nin,
self.dat0_datac1_freedv,
self.dat0_datac1_bytes_out,
self.dat0_datac1_bytes_per_frame,
self.DAT0_DATAC1_STATE,
"dat0-datac1"
)
def audio_dat0_datac3(self) -> None:
"""Receive data encoded with datac3"""
self.dat0_datac3_nin = self.demodulate_audio(
self.dat0_datac3_buffer,
self.dat0_datac3_nin,
self.dat0_datac3_freedv,
self.dat0_datac3_bytes_out,
self.dat0_datac3_bytes_per_frame,
self.DAT0_DATAC3_STATE,
"dat0-datac3"
)
def audio_fsk_ldpc_0(self) -> None:
"""Receive data encoded with FSK + LDPC0"""
self.fsk_ldpc_nin_0 = self.demodulate_audio(
self.fsk_ldpc_buffer_0,
self.fsk_ldpc_nin_0,
self.fsk_ldpc_freedv_0,
self.fsk_ldpc_bytes_out_0,
self.fsk_ldpc_bytes_per_frame_0,
self.FSK_LDPC0_STATE,
"fsk_ldpc0",
)
def audio_fsk_ldpc_1(self) -> None:
"""Receive data encoded with FSK + LDPC1"""
self.fsk_ldpc_nin_1 = self.demodulate_audio(
self.fsk_ldpc_buffer_1,
self.fsk_ldpc_nin_1,
self.fsk_ldpc_freedv_1,
self.fsk_ldpc_bytes_out_1,
self.fsk_ldpc_bytes_per_frame_1,
self.FSK_LDPC1_STATE,
"fsk_ldpc1",
)
def sd_input_audio_callback(self, indata: np.ndarray, frames: int, time, status) -> None: def sd_input_audio_callback(self, indata: np.ndarray, frames: int, time, status) -> None:
x = np.frombuffer(indata, dtype=np.int16) audio_48k = np.frombuffer(indata, dtype=np.int16)
x = self.resampler.resample48_to_8(x) audio_8k = self.resampler.resample48_to_8(audio_48k)
x = audio.set_audio_volume(x, self.rx_audio_level) audio.calculate_fft(audio_8k, self.fft_queue, self.states)
# audio recording for debugging purposes audio_8k_level_adjusted = audio.set_audio_volume(audio_8k, self.rx_audio_level)
# TODO Find a nice place for this
#if AudioParam.audio_record:
# AudioParam.audio_record_file.writeframes(x)
# Avoid decoding when transmitting to reduce CPU length_audio_8k_level_adjusted = len(audio_8k_level_adjusted)
# TODO Overriding this for testing purposes
# if not self.states.is_transmitting:
length_x = len(x)
# Avoid buffer overflow by filling only if buffer for # Avoid buffer overflow by filling only if buffer for
# selected datachannel mode is not full # selected datachannel mode is not full
for audiobuffer, receive, index in [ index = 0
(self.sig0_datac13_buffer, self.RECEIVE_SIG0, 0), for mode in self.MODE_DICT:
(self.sig1_datac13_buffer, self.RECEIVE_SIG1, 1), mode_data = self.MODE_DICT[mode]
(self.dat0_datac1_buffer, self.RECEIVE_DATAC1, 2), audiobuffer = mode_data['audio_buffer']
(self.dat0_datac3_buffer, self.RECEIVE_DATAC3, 3), decode = mode_data['decode']
(self.dat0_datac4_buffer, self.RECEIVE_DATAC4, 4), index += 1
(self.fsk_ldpc_buffer_0, self.enable_fsk, 5), if audiobuffer:
(self.fsk_ldpc_buffer_1, self.enable_fsk, 6), if (audiobuffer.nbuffer + length_audio_8k_level_adjusted) > audiobuffer.size:
]: self.buffer_overflow_counter[index] += 1
if (audiobuffer.nbuffer + length_x) > audiobuffer.size: self.event_manager.send_buffer_overflow(self.buffer_overflow_counter)
self.buffer_overflow_counter[index] += 1 elif decode:
self.event_manager.send_buffer_overflow(self.buffer_overflow_counter) audiobuffer.push(audio_8k_level_adjusted)
elif receive:
audiobuffer.push(x)
return x
def worker_received(self) -> None: def worker_received(self) -> None:
"""Worker for FIFO queue for processing received frames""" """Worker for FIFO queue for processing received frames"""
@ -389,40 +189,20 @@ class Demodulator():
offset = round(modemStats.foff) * (-1) offset = round(modemStats.foff) * (-1)
return offset return offset
def demodulate_audio( def demodulate_audio(self, mode) -> int:
self,
audiobuffer: codec2.audio_buffer,
nin: int,
freedv: ctypes.c_void_p,
bytes_out,
bytes_per_frame,
state_buffer,
mode_name,
) -> int:
""" """
De-modulate supplied audio stream with supplied codec2 instance. De-modulate supplied audio stream with supplied codec2 instance.
Decoded audio is placed into `bytes_out`. Decoded audio is placed into `bytes_out`.
:param audiobuffer: Incoming audio
:type audiobuffer: codec2.audio_buffer
:param nin: Number of frames codec2 is expecting
:type nin: int
:param freedv: codec2 instance
:type freedv: ctypes.c_void_p
:param bytes_out: Demodulated audio
:type bytes_out: _type_
:param bytes_per_frame: Number of bytes per frame
:type bytes_per_frame: int
:param state_buffer: modem states
:type state_buffer: int
:param mode_name: mode name
:type mode_name: str
:return: NIN from freedv instance
:rtype: int
""" """
nbytes = 0 audiobuffer = self.MODE_DICT[mode]["audio_buffer"]
#try: nin = self.MODE_DICT[mode]["nin"]
freedv = self.MODE_DICT[mode]["instance"]
bytes_out = self.MODE_DICT[mode]["bytes_out"]
bytes_per_frame= self.MODE_DICT[mode]["bytes_per_frame"]
state_buffer = self.MODE_DICT[mode]["state_buffer"]
mode_name = self.MODE_DICT[mode]["name"]
while self.stream.active: while self.stream.active:
threading.Event().wait(0.01) threading.Event().wait(0.01)
while audiobuffer.nbuffer >= nin: while audiobuffer.nbuffer >= nin:
@ -440,13 +220,12 @@ class Demodulator():
if rx_status not in [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: self.states.set("is_codec2_traffic", True)
self.states.set("is_codec2_traffic", True) self.is_codec2_traffic_counter = self.is_codec2_traffic_cooldown
self.is_codec2_traffic_counter = self.is_codec2_traffic_cooldown if not self.states.channel_busy:
if not self.states.channel_busy: self.log.debug("[MDM] Setting channel_busy since codec2 data detected")
self.log.debug("[MDM] Setting channel_busy since codec2 data detected") self.states.set("channel_busy", True)
self.states.set("channel_busy", True) #self.channel_busy_delay += 10
#self.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]
@ -469,18 +248,7 @@ class Demodulator():
if nbytes == bytes_per_frame: if nbytes == bytes_per_frame:
print(bytes(bytes_out)) print(bytes(bytes_out))
# ignore data channel opener frames for avoiding toggle states if int.from_bytes(bytes(bytes_out[:1]), "big") in [
# use case: opener already received, but ack got lost and we are receiving
# an opener again
if mode_name in ["sig1-datac13"] and int.from_bytes(bytes(bytes_out[:1]), "big") in [
FRAME_TYPE.ARQ_SESSION_OPEN.value,
FRAME_TYPE.ARQ_DC_OPEN_W.value,
FRAME_TYPE.ARQ_DC_OPEN_ACK_W.value,
FRAME_TYPE.ARQ_DC_OPEN_N.value,
FRAME_TYPE.ARQ_DC_OPEN_ACK_N.value
]:
print("dropp")
elif int.from_bytes(bytes(bytes_out[:1]), "big") in [
FRAME_TYPE.MESH_BROADCAST.value, FRAME_TYPE.MESH_BROADCAST.value,
FRAME_TYPE.MESH_SIGNALLING_PING.value, FRAME_TYPE.MESH_SIGNALLING_PING.value,
FRAME_TYPE.MESH_SIGNALLING_PING_ACK.value, FRAME_TYPE.MESH_SIGNALLING_PING_ACK.value,
@ -499,11 +267,6 @@ class Demodulator():
self.get_scatter(freedv) self.get_scatter(freedv)
state_buffer = [] state_buffer = []
#except Exception as e:
# self.log.warning("[MDM] [demod_audio] Stream not active anymore", e=e)
return nin
def tci_rx_callback(self) -> None: def tci_rx_callback(self) -> None:
""" """
Callback for TCI RX Callback for TCI RX
@ -514,61 +277,26 @@ class Demodulator():
while True: while True:
x = self.audio_received_queue.get() audio_48k = self.audio_received_queue.get()
x = np.frombuffer(x, dtype=np.int16) audio_48k = np.frombuffer(audio_48k, dtype=np.int16)
# x = self.resampler.resample48_to_8(x)
self.calculate_fft(x)
length_x = len(x) audio.calculate_fft(audio_48k, self.fft_queue, self.states)
for data_buffer, receive in [
(self.sig0_datac13_buffer, self.RECEIVE_SIG0),
(self.sig1_datac13_buffer, self.RECEIVE_SIG1),
(self.dat0_datac1_buffer, self.RECEIVE_DATAC1),
(self.dat0_datac3_buffer, self.RECEIVE_DATAC3),
(self.dat0_datac4_buffer, self.RECEIVE_DATAC4),
(self.fsk_ldpc_buffer_0, self.enable_fsk),
(self.fsk_ldpc_buffer_1, self.enable_fsk),
]:
if (
not (data_buffer.nbuffer + length_x) > data_buffer.size
and receive
):
data_buffer.push(x)
def mkfifo_read_callback(self) -> None: length_audio_48k = len(audio_48k)
""" index = 0
Support testing by reading the audio data from a pipe and for mode in self.MODE_DICT:
depositing the data into the codec data buffers. mode_data = self.MODE_DICT[mode]
""" audiobuffer = mode_data['audio_buffer']
while True: decode = mode_data['decode']
threading.Event().wait(0.01) index += 1
# -----read if audiobuffer:
data_in48k = bytes() if (audiobuffer.nbuffer + length_audio_48k) > audiobuffer.size:
with open("", "rb") as fifo: self.buffer_overflow_counter[index] += 1
for line in fifo: self.event_manager.send_buffer_overflow(self.buffer_overflow_counter)
data_in48k += line elif decode:
audiobuffer.push(audio_48k)
while len(data_in48k) >= 48:
x = np.frombuffer(data_in48k[:48], dtype=np.int16)
x = self.resampler.resample48_to_8(x)
data_in48k = data_in48k[48:]
length_x = len(x)
for data_buffer, receive in [
(self.sig0_datac13_buffer, self.RECEIVE_SIG0),
(self.sig1_datac13_buffer, self.RECEIVE_SIG1),
(self.dat0_datac1_buffer, self.RECEIVE_DATAC1),
(self.dat0_datac3_buffer, self.RECEIVE_DATAC3),
(self.dat0_datac4_buffer, self.RECEIVE_DATAC4),
(self.fsk_ldpc_buffer_0, self.enable_fsk),
(self.fsk_ldpc_buffer_1, self.enable_fsk),
]:
if (
not (data_buffer.nbuffer + length_x) > data_buffer.size
and receive
):
data_buffer.push(x)
def set_frames_per_burst(self, frames_per_burst: int) -> None: def set_frames_per_burst(self, frames_per_burst: int) -> None:
""" """
@ -586,7 +314,6 @@ class Demodulator():
codec2.api.freedv_set_frames_per_burst(self.dat0_datac1_freedv, frames_per_burst) codec2.api.freedv_set_frames_per_burst(self.dat0_datac1_freedv, frames_per_burst)
codec2.api.freedv_set_frames_per_burst(self.dat0_datac3_freedv, frames_per_burst) codec2.api.freedv_set_frames_per_burst(self.dat0_datac3_freedv, frames_per_burst)
codec2.api.freedv_set_frames_per_burst(self.dat0_datac4_freedv, frames_per_burst) codec2.api.freedv_set_frames_per_burst(self.dat0_datac4_freedv, frames_per_burst)
codec2.api.freedv_set_frames_per_burst(self.fsk_ldpc_freedv_0, frames_per_burst)
def calculate_snr(self, freedv: ctypes.c_void_p) -> float: def calculate_snr(self, freedv: ctypes.c_void_p) -> float:
""" """
@ -659,13 +386,11 @@ class Demodulator():
def reset_data_sync(self) -> None: def reset_data_sync(self) -> None:
""" """
reset sync state for data modes reset sync state for modes
:param frames_per_burst: Number of frames per burst requested :param frames_per_burst: Number of frames per burst requested
:type frames_per_burst: int :type frames_per_burst: int
""" """
for mode in self.MODE_DICT:
codec2.api.freedv_set_sync(self.MODE_DICT[mode]["instance"], 0)
codec2.api.freedv_set_sync(self.dat0_datac1_freedv, 0)
codec2.api.freedv_set_sync(self.dat0_datac3_freedv, 0)
codec2.api.freedv_set_sync(self.dat0_datac4_freedv, 0)
codec2.api.freedv_set_sync(self.fsk_ldpc_freedv_0, 0)

View file

@ -93,7 +93,6 @@ class DISPATCHER():
self.states, self.states,
self.event_manager, self.event_manager,
self.modem) self.modem)
handler.handle(deconstructed_frame, snr, frequency_offset, freedv, bytes_per_frame) handler.handle(deconstructed_frame, snr, frequency_offset, freedv, bytes_per_frame)
def get_id_from_frame(self, data): def get_id_from_frame(self, data):

View file

@ -6,6 +6,8 @@ import structlog
import time, uuid import time, uuid
from codec2 import FREEDV_MODE from codec2 import FREEDV_MODE
TESTMODE = False
class FrameHandler(): class FrameHandler():
def __init__(self, name: str, config, states: StateManager, event_manager: EventManager, def __init__(self, name: str, config, states: StateManager, event_manager: EventManager,
@ -87,14 +89,13 @@ class FrameHandler():
self.event_manager.broadcast(event_data) self.event_manager.broadcast(event_data)
def get_tx_mode(self): def get_tx_mode(self):
return ( return FREEDV_MODE.signalling.value
FREEDV_MODE.fsk_ldpc_0.value
if self.config['MODEM']['enable_fsk']
else FREEDV_MODE.sig0.value
)
def transmit(self, frame): def transmit(self, frame):
self.modem.transmit(self.get_tx_mode(), 1, 0, frame) if not TESTMODE:
self.modem.transmit(self.get_tx_mode(), 1, 0, frame)
else:
self.event_manager.broadcast(frame)
def follow_protocol(self): def follow_protocol(self):
pass pass

View file

@ -322,9 +322,10 @@ def check_callsign(callsign: str, crc_to_check: bytes, ssid_list):
#call_with_ssid.extend(str(ssid).encode("utf-8")) #call_with_ssid.extend(str(ssid).encode("utf-8"))
callsign_crc = get_crc_24(call_with_ssid) callsign_crc = get_crc_24(call_with_ssid)
callsign_crc = callsign_crc.hex()
if callsign_crc == crc_to_check: if callsign_crc == crc_to_check:
log.debug("[HLP] check_callsign matched:", call_with_ssid=call_with_ssid) log.debug("[HLP] check_callsign matched:", call_with_ssid=call_with_ssid, checksum=crc_to_check)
return [True, call_with_ssid.decode()] return [True, call_with_ssid.decode()]
return [False, b''] return [False, b'']

View file

@ -14,7 +14,6 @@ import ctypes
import queue import queue
import threading import threading
import time import time
from collections import deque
import codec2 import codec2
import numpy as np import numpy as np
import sounddevice as sd import sounddevice as sd
@ -27,6 +26,8 @@ import event_manager
import beacon import beacon
import demodulator import demodulator
TESTMODE = False
class RF: class RF:
"""Class to encapsulate interactions between the audio device and codec2""" """Class to encapsulate interactions between the audio device and codec2"""
@ -47,8 +48,6 @@ class RF:
self.tx_audio_level = config['AUDIO']['tx_audio_level'] self.tx_audio_level = config['AUDIO']['tx_audio_level']
self.enable_audio_auto_tune = config['AUDIO']['enable_auto_tune'] self.enable_audio_auto_tune = config['AUDIO']['enable_auto_tune']
#Dynamically enable FFT data stream when a client connects to FFT web socket
self.enable_fft_stream = False
self.tx_delay = config['MODEM']['tx_delay'] self.tx_delay = config['MODEM']['tx_delay']
self.radiocontrol = config['RADIO']['control'] self.radiocontrol = config['RADIO']['control']
@ -63,7 +62,6 @@ class RF:
self.tci_ip = config['TCI']['tci_ip'] self.tci_ip = config['TCI']['tci_ip']
self.tci_port = config['TCI']['tci_port'] self.tci_port = config['TCI']['tci_port']
self.channel_busy_delay = 0
self.AUDIO_SAMPLE_RATE = 48000 self.AUDIO_SAMPLE_RATE = 48000
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000 self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
@ -99,7 +97,9 @@ class RF:
self.modem_received_queue, self.modem_received_queue,
self.data_queue_received, self.data_queue_received,
self.states, self.states,
self.event_manager) self.event_manager,
self.fft_queue
)
self.beacon = beacon.Beacon(self.config, self.states, event_queue, self.beacon = beacon.Beacon(self.config, self.states, event_queue,
self.log, self.modem_transmit_queue) self.log, self.modem_transmit_queue)
@ -110,13 +110,14 @@ class RF:
self.tci_module.push_audio(audio_48k) self.tci_module.push_audio(audio_48k)
def start_modem(self): def start_modem(self):
result = False # testmode: We need to call the modem without audio parts for running protocol tests
if self.radiocontrol not in ["tci"]: if self.radiocontrol not in ["tci"]:
result = self.init_audio() result = self.init_audio() if not TESTMODE else True
if not result: if not result:
raise RuntimeError("Unable to init audio devices") raise RuntimeError("Unable to init audio devices")
self.demodulator.start(self.sd_input_stream) if not TESTMODE:
self.demodulator.start(self.sd_input_stream)
else: else:
result = self.init_tci() result = self.init_tci()
@ -130,7 +131,8 @@ class RF:
# init data thread # init data thread
self.init_data_threads() self.init_data_threads()
atexit.register(self.sd_input_stream.stop) if not TESTMODE:
atexit.register(self.sd_input_stream.stop)
# init beacon # init beacon
self.beacon.start() self.beacon.start()
@ -227,6 +229,7 @@ class RF:
daemon=True, daemon=True,
) )
tci_tx_callback_thread.start() tci_tx_callback_thread.start()
return True
def audio_auto_tune(self): def audio_auto_tune(self):
# enable / disable AUDIO TUNE Feature / ALC correction # enable / disable AUDIO TUNE Feature / ALC correction
@ -269,14 +272,19 @@ class RF:
frames: frames:
""" """
if TESTMODE:
return
self.demodulator.reset_data_sync() self.demodulator.reset_data_sync()
# get freedv instance by mode # get freedv instance by mode
mode_transition = { mode_transition = {
codec2.FREEDV_MODE.datac0.value: self.freedv_datac0_tx, codec2.FREEDV_MODE.signalling: self.freedv_datac13_tx,
codec2.FREEDV_MODE.datac1.value: self.freedv_datac1_tx, codec2.FREEDV_MODE.datac0: self.freedv_datac0_tx,
codec2.FREEDV_MODE.datac3.value: self.freedv_datac3_tx, codec2.FREEDV_MODE.datac1: self.freedv_datac1_tx,
codec2.FREEDV_MODE.datac4.value: self.freedv_datac4_tx, codec2.FREEDV_MODE.datac3: self.freedv_datac3_tx,
codec2.FREEDV_MODE.datac13.value: self.freedv_datac13_tx, codec2.FREEDV_MODE.datac4: self.freedv_datac4_tx,
codec2.FREEDV_MODE.datac13: self.freedv_datac13_tx,
} }
if mode in mode_transition: if mode in mode_transition:
freedv = mode_transition[mode] freedv = mode_transition[mode]
@ -341,16 +349,10 @@ class RF:
# Create modulation for all frames in the list # Create modulation for all frames in the list
for frame in frames: for frame in frames:
# Write preamble to txbuffer # Write preamble to txbuffer
# codec2 fsk preamble may be broken - codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble)
# at least it sounds like that, so we are disabling it for testing txbuffer += bytes(mod_out_preamble)
if self.MODE not in [
codec2.FREEDV_MODE.fsk_ldpc_0.value,
codec2.FREEDV_MODE.fsk_ldpc_1.value,
]:
# Write preamble to txbuffer
codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble)
txbuffer += bytes(mod_out_preamble)
# Create buffer for data # Create buffer for data
# Use this if CRC16 checksum is required (DATAc1-3) # Use this if CRC16 checksum is required (DATAc1-3)
@ -375,16 +377,10 @@ class RF:
codec2.api.freedv_rawdatatx(freedv, mod_out, data) codec2.api.freedv_rawdatatx(freedv, mod_out, data)
txbuffer += bytes(mod_out) txbuffer += bytes(mod_out)
# codec2 fsk postamble may be broken - # Write postamble to txbuffer
# at least it sounds like that, so we are disabling it for testing codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble)
if self.MODE not in [ # Append postamble to txbuffer
codec2.FREEDV_MODE.fsk_ldpc_0.value, txbuffer += bytes(mod_out_postamble)
codec2.FREEDV_MODE.fsk_ldpc_1.value,
]:
# Write postamble to txbuffer
codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble)
# Append postamble to txbuffer
txbuffer += bytes(mod_out_postamble)
# Add delay to end of frames # Add delay to end of frames
samples_delay = int(self.MODEM_SAMPLE_RATE * (repeat_delay / 1000)) # type: ignore samples_delay = int(self.MODEM_SAMPLE_RATE * (repeat_delay / 1000)) # type: ignore
@ -451,6 +447,7 @@ class RF:
end_of_transmission = time.time() end_of_transmission = time.time()
transmission_time = end_of_transmission - start_of_transmission transmission_time = end_of_transmission - start_of_transmission
self.log.debug("[MDM] ON AIR TIME", time=transmission_time) self.log.debug("[MDM] ON AIR TIME", time=transmission_time)
return True
def transmit_morse(self, repeats, repeat_delay, frames): def transmit_morse(self, repeats, repeat_delay, frames):
self.states.waitForTransmission() self.states.waitForTransmission()
@ -516,8 +513,6 @@ class RF:
self.freedv_datac3_tx = codec2.open_instance(codec2.FREEDV_MODE.datac3.value) self.freedv_datac3_tx = codec2.open_instance(codec2.FREEDV_MODE.datac3.value)
self.freedv_datac4_tx = codec2.open_instance(codec2.FREEDV_MODE.datac4.value) self.freedv_datac4_tx = codec2.open_instance(codec2.FREEDV_MODE.datac4.value)
self.freedv_datac13_tx = codec2.open_instance(codec2.FREEDV_MODE.datac13.value) self.freedv_datac13_tx = codec2.open_instance(codec2.FREEDV_MODE.datac13.value)
self.freedv_ldpc0_tx = codec2.open_instance(codec2.FREEDV_MODE.fsk_ldpc_0.value)
self.freedv_ldpc1_tx = codec2.open_instance(codec2.FREEDV_MODE.fsk_ldpc_1.value)
def init_data_threads(self): def init_data_threads(self):
worker_received = threading.Thread( worker_received = threading.Thread(
@ -529,7 +524,6 @@ class RF:
def transmit_audio(self, audio_48k) -> None: def transmit_audio(self, audio_48k) -> None:
self.radio.set_ptt(True) self.radio.set_ptt(True)
self.event_manager.send_ptt_change(True) self.event_manager.send_ptt_change(True)
self.calculate_fft(audio_48k)
if self.radiocontrol in ["tci"]: if self.radiocontrol in ["tci"]:
self.tci_tx_callback(audio_48k) self.tci_tx_callback(audio_48k)
@ -606,123 +600,3 @@ class RF:
) )
threading.Event().wait(1) threading.Event().wait(1)
def calculate_fft(self, data) -> None:
"""
Calculate an average signal strength of the channel to assess
whether the channel is "busy."
"""
# Initialize dbfs counter
# rms_counter = 0
# https://gist.github.com/ZWMiller/53232427efc5088007cab6feee7c6e4c
# Fast Fourier Transform, 10*log10(abs) is to scale it to dB
# and make sure it's not imaginary
try:
fftarray = np.fft.rfft(data)
# Set value 0 to 1 to avoid division by zero
fftarray[fftarray == 0] = 1
dfft = 10.0 * np.log10(abs(fftarray))
# get average of dfft
avg = np.mean(dfft)
# Detect signals which are higher than the
# average + 10 (+10 smoothes the output).
# Data higher than the average must be a signal.
# Therefore we are setting it to 100 so it will be highlighted
# Have to do this when we are not transmitting so our
# own sending data will not affect this too much
if not self.states.isTransmitting():
dfft[dfft > avg + 15] = 100
# Calculate audio dbfs
# https://stackoverflow.com/a/9763652
# calculate dbfs every 50 cycles for reducing CPU load
self.rms_counter += 1
if self.rms_counter > 5:
d = np.frombuffer(data, np.int16).astype(np.float32)
# calculate RMS and then dBFS
# https://dsp.stackexchange.com/questions/8785/how-to-compute-dbfs
# try except for avoiding runtime errors by division/0
try:
rms = int(np.sqrt(np.max(d ** 2)))
if rms == 0:
raise ZeroDivisionError
audio_dbfs = 20 * np.log10(rms / 32768)
self.states.set("audio_dbfs", audio_dbfs)
except Exception as e:
self.states.set("audio_dbfs", -100)
self.rms_counter = 0
# Convert data to int to decrease size
dfft = dfft.astype(int)
# Create list of dfft
dfftlist = dfft.tolist()
# Reduce area where the busy detection is enabled
# We want to have this in correlation with mode bandwidth
# TODO This is not correctly and needs to be checked for correct maths
# dfftlist[0:1] = 10,15Hz
# Bandwidth[Hz] / 10,15
# narrowband = 563Hz = 56
# wideband = 1700Hz = 167
# 1500Hz = 148
# 2700Hz = 266
# 3200Hz = 315
# slot
slot = 0
slot1 = [0, 65]
slot2 = [65,120]
slot3 = [120, 176]
slot4 = [176, 231]
slot5 = [231, len(dfftlist)]
slotbusy = [False,False,False,False,False]
# Set to true if we should increment delay count; else false to decrement
addDelay=False
for range in [slot1, slot2, slot3, slot4, slot5]:
range_start = range[0]
range_end = range[1]
# define the area, we are detecting busy state
slotdfft = dfft[range_start:range_end]
# Check for signals higher than average by checking for "100"
# If we have a signal, increment our channel_busy delay counter
# so we have a smoother state toggle
if np.sum(slotdfft[slotdfft > avg + 15]) >= 200 and not self.states.isTransmitting():
addDelay=True
slotbusy[slot]=True
#self.states.channel_busy_slot[slot] = True
# increment slot
slot += 1
self.states.set_channel_slot_busy(slotbusy)
if addDelay:
# Limit delay counter to a maximum of 200. The higher this value,
# the longer we will wait until releasing state
self.states.set("channel_busy", True)
self.channel_busy_delay = min(self.channel_busy_delay + 10, 200)
else:
# Decrement channel busy counter if no signal has been detected.
self.channel_busy_delay = max(self.channel_busy_delay - 1, 0)
# When our channel busy counter reaches 0, toggle state to False
if self.channel_busy_delay == 0:
self.states.set("channel_busy", False)
if (self.enable_fft_stream):
# erase queue if greater than 10
if self.fft_queue.qsize() >= 10:
self.fft_queue = queue.Queue()
self.fft_queue.put(dfftlist[:315]) # 315 --> bandwidth 3200
except Exception as err:
self.log.error(f"[MDM] calculate_fft: Exception: {err}")
self.log.debug("[MDM] Setting fft=0")
# else 0
self.fft_queue.put([0])
def set_FFT_stream(self, enable: bool):
# Set config boolean regarding wheter it should sent FFT data to queue
self.enable_fft_stream = enable

View file

@ -240,9 +240,6 @@ def sock_events(sock):
@sock.route('/fft') @sock.route('/fft')
def sock_fft(sock): def sock_fft(sock):
if len(wsm.fft_client_list) == 0:
app.modem_service.put("fft:true")
print("Streaming data to FFT socket since a client is connected")
wsm.handle_connection(sock, wsm.fft_client_list, app.modem_fft) wsm.handle_connection(sock, wsm.fft_client_list, app.modem_fft)
@sock.route('/states') @sock.route('/states')

View file

@ -17,7 +17,6 @@ class SM:
self.config = self.app.config_manager.read() self.config = self.app.config_manager.read()
self.modem_events = app.modem_events self.modem_events = app.modem_events
self.modem_fft = app.modem_fft self.modem_fft = app.modem_fft
self.enable_fft_stream = False
self.modem_service = app.modem_service self.modem_service = app.modem_service
self.states = app.state_manager self.states = app.state_manager
@ -49,14 +48,6 @@ class SM:
threading.Event().wait(0.5) threading.Event().wait(0.5)
if self.start_modem(): if self.start_modem():
self.modem_events.put(json.dumps({"freedata": "modem-event", "event": "restart"})) self.modem_events.put(json.dumps({"freedata": "modem-event", "event": "restart"}))
elif cmd in ['fft:true']:
# Tell modem it should put FFT data in the queue
self.modem.set_FFT_stream(True)
self.enable_fft_stream=True
elif cmd in ['fft:false']:
# Tell modem it should not put FFT data in the queue
self.modem.set_FFT_stream(False)
self.enable_fft_stream=False
else: else:
self.log.warning("[SVC] modem command processing failed", cmd=cmd, state=self.states.is_modem_running) self.log.warning("[SVC] modem command processing failed", cmd=cmd, state=self.states.is_modem_running)
@ -88,7 +79,6 @@ class SM:
self.frame_dispatcher.start() self.frame_dispatcher.start()
self.states.set("is_modem_running", True) self.states.set("is_modem_running", True)
self.modem.set_FFT_stream(self.enable_fft_stream)
self.modem.start_modem() self.modem.start_modem()
return True return True

View file

@ -61,8 +61,8 @@ class TestDataFrameFactory(unittest.TestCase):
FREEDV_MODE.datac3, session_id, offset, payload) FREEDV_MODE.datac3, session_id, offset, payload)
def testAvailablePayload(self): def testAvailablePayload(self):
avail = self.factory.get_available_data_payload_for_mode(FRAME_TYPE.BURST_FRAME, FREEDV_MODE.datac3) avail = self.factory.get_available_data_payload_for_mode(FRAME_TYPE.ARQ_BURST_FRAME, FREEDV_MODE.datac3)
self.assertEqual(avail, 123) # 128 bytes datac3 frame payload - BURST frame overhead self.assertEqual(avail, 120) # 128 bytes datac3 frame payload - BURST frame overhead
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View file

@ -9,6 +9,9 @@ import queue
from state_manager import StateManager from state_manager import StateManager
from command_ping import PingCommand from command_ping import PingCommand
from command_cq import CQCommand from command_cq import CQCommand
import modem
import frame_handler
class TestProtocols(unittest.TestCase): class TestProtocols(unittest.TestCase):
@ -22,18 +25,19 @@ class TestProtocols(unittest.TestCase):
cls.event_queue = queue.Queue() cls.event_queue = queue.Queue()
cls.data_queue_received = queue.Queue()
cls.modem_transmit_queue = queue.Queue() cls.modem_transmit_queue = queue.Queue()
cls.modem = modem.RF(cls.config, cls.event_queue, queue.Queue(), queue.Queue(), cls.state_manager)
modem.TESTMODE = True
frame_handler.TESTMODE = True
#cls.modem.start_modem()
cls.frame_dispatcher = DISPATCHER(cls.config, cls.frame_dispatcher = DISPATCHER(cls.config,
cls.event_queue, cls.event_queue,
cls.state_manager, cls.state_manager,
cls.data_queue_received, cls.modem)
cls.modem_transmit_queue)
def shortcutTransmission(self): def shortcutTransmission(self, frame_bytes):
transmission_item = self.modem_transmit_queue.get()
frame_bytes = bytes(transmission_item['frame'])
self.frame_dispatcher.new_process_data(frame_bytes, None, len(frame_bytes), 0, 0) self.frame_dispatcher.new_process_data(frame_bytes, None, len(frame_bytes), 0, 0)
def assertEventReceivedType(self, event_type): def assertEventReceivedType(self, event_type):
@ -44,30 +48,36 @@ class TestProtocols(unittest.TestCase):
def testPingWithAck(self): def testPingWithAck(self):
# Run ping command # Run ping command
api_params = { "dxcall": "XX1XXX-7" } api_params = { "dxcall": "XX1XXX-7"}
ping_cmd = PingCommand(self.config, self.state_manager, self.event_queue, api_params) ping_cmd = PingCommand(self.config, self.state_manager, self.event_queue, api_params)
ping_cmd.run(self.event_queue, self.modem_transmit_queue) #ping_cmd.run(self.event_queue, self.modem)
frame = ping_cmd.test(self.event_queue)
# Shortcut the transmit queue directly to the frame dispatcher # Shortcut the transmit queue directly to the frame dispatcher
self.shortcutTransmission() self.shortcutTransmission(frame)
self.assertEventReceivedType('PING') self.assertEventReceivedType('PING')
event_frame = self.event_queue.get()
# Check ACK # Check ACK
self.shortcutTransmission() self.shortcutTransmission(event_frame)
self.assertEventReceivedType('PING_ACK') self.assertEventReceivedType('PING_ACK')
print("PING/PING ACK CHECK SUCCESSFULLY")
def testCQWithQRV(self): def testCQWithQRV(self):
self.config['MODEM']['respond_to_cq'] = True self.config['MODEM']['respond_to_cq'] = True
api_params = {} api_params = {}
cmd = CQCommand(self.config, self.state_manager, self.event_queue, api_params) cmd = CQCommand(self.config, self.state_manager, self.event_queue, api_params)
cmd.run(self.event_queue, self.modem_transmit_queue) #cmd.run(self.event_queue, self.modem)
frame = cmd.test(self.event_queue)
self.shortcutTransmission() self.shortcutTransmission(frame)
self.assertEventReceivedType('CQ') self.assertEventReceivedType('CQ')
self.shortcutTransmission() event_frame = self.event_queue.get()
# Check QRV
self.shortcutTransmission(event_frame)
self.assertEventReceivedType('QRV') self.assertEventReceivedType('QRV')
print("CQ/QRV CHECK SUCCESSFULLY")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()