mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
Merge branch 'develop' of github.com:DJ2LS/FreeDATA into develop
This commit is contained in:
commit
85689a4d37
|
@ -56,9 +56,6 @@ class ARQSessionIRS(arq_session.ARQSession):
|
|||
|
||||
self.transmitted_acks = 0
|
||||
|
||||
def set_modem_decode_modes(self, modes):
|
||||
pass
|
||||
|
||||
def all_data_received(self):
|
||||
return self.received_bytes == len(self.received_data)
|
||||
|
||||
|
@ -90,7 +87,7 @@ class ARQSessionIRS(arq_session.ARQSession):
|
|||
self.dxcall,
|
||||
self.version,
|
||||
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)
|
||||
|
||||
def send_info_ack(self, info_frame):
|
||||
|
@ -100,23 +97,33 @@ class ARQSessionIRS(arq_session.ARQSession):
|
|||
self.dx_snr.append(info_frame['snr'])
|
||||
|
||||
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(
|
||||
self.id, self.total_crc, self.snr[0],
|
||||
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)
|
||||
|
||||
def send_burst_nack(self):
|
||||
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])
|
||||
self.transmit_and_wait(nack)
|
||||
|
||||
|
||||
def set_modem_listening_modes(self, speed_level):
|
||||
# TODO
|
||||
# We want to set the modems listening modes somehow...
|
||||
def set_modem_decode_modes(self, speed_level):
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ class ARQSessionISS(arq_session.ARQSession):
|
|||
|
||||
def start(self):
|
||||
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)
|
||||
|
||||
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),
|
||||
helpers.get_crc_32(self.data),
|
||||
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)
|
||||
|
||||
def send_data(self, irs_frame):
|
||||
|
|
122
modem/audio.py
122
modem/audio.py
|
@ -7,6 +7,7 @@ import crcengine
|
|||
import sounddevice as sd
|
||||
import structlog
|
||||
import numpy as np
|
||||
import queue
|
||||
|
||||
atexit.register(sd._terminate)
|
||||
|
||||
|
@ -207,3 +208,124 @@ def set_audio_volume(datalist: np.ndarray, dB: float) -> np.ndarray:
|
|||
|
||||
# Clip values to int16 range and convert data type
|
||||
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])
|
||||
|
|
|
@ -24,16 +24,12 @@ class FREEDV_MODE(Enum):
|
|||
"""
|
||||
Enumeration for codec2 modes and names
|
||||
"""
|
||||
sig0 = 19
|
||||
sig1 = 19
|
||||
signalling = 19
|
||||
datac0 = 14
|
||||
datac1 = 10
|
||||
datac3 = 12
|
||||
datac4 = 18
|
||||
datac13 = 19
|
||||
fsk_ldpc = 9
|
||||
fsk_ldpc_0 = 200
|
||||
fsk_ldpc_1 = 201
|
||||
|
||||
|
||||
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
|
||||
:rtype: ctypes.c_void_p
|
||||
"""
|
||||
if mode in [FREEDV_MODE.fsk_ldpc_0.value]:
|
||||
return ctypes.cast(
|
||||
api.freedv_open_advanced(
|
||||
FREEDV_MODE.fsk_ldpc.value,
|
||||
ctypes.byref(api.FREEDV_MODE_FSK_LDPC_0_ADV),
|
||||
),
|
||||
ctypes.c_void_p,
|
||||
)
|
||||
|
||||
if mode in [FREEDV_MODE.fsk_ldpc_1.value]:
|
||||
return ctypes.cast(
|
||||
api.freedv_open_advanced(
|
||||
FREEDV_MODE.fsk_ldpc.value,
|
||||
ctypes.byref(api.FREEDV_MODE_FSK_LDPC_1_ADV),
|
||||
),
|
||||
ctypes.c_void_p,
|
||||
)
|
||||
|
||||
# if mode in [FREEDV_MODE.fsk_ldpc_0.value]:
|
||||
# return ctypes.cast(
|
||||
# api.freedv_open_advanced(
|
||||
# FREEDV_MODE.fsk_ldpc.value,
|
||||
# ctypes.byref(api.FREEDV_MODE_FSK_LDPC_0_ADV),
|
||||
# ),
|
||||
# ctypes.c_void_p,
|
||||
# )
|
||||
#
|
||||
# if mode in [FREEDV_MODE.fsk_ldpc_1.value]:
|
||||
# return ctypes.cast(
|
||||
# api.freedv_open_advanced(
|
||||
# FREEDV_MODE.fsk_ldpc.value,
|
||||
# ctypes.byref(api.FREEDV_MODE_FSK_LDPC_1_ADV),
|
||||
# ),
|
||||
# ctypes.c_void_p,
|
||||
# )
|
||||
#
|
||||
return ctypes.cast(api.freedv_open(mode), ctypes.c_void_p)
|
||||
|
||||
def get_bytes_per_frame(mode: int) -> int:
|
||||
|
|
|
@ -30,11 +30,7 @@ class TxCommand():
|
|||
pass
|
||||
|
||||
def get_tx_mode(self):
|
||||
return (
|
||||
FREEDV_MODE.fsk_ldpc_0.value
|
||||
if self.config['MODEM']['enable_fsk']
|
||||
else FREEDV_MODE.sig0.value
|
||||
)
|
||||
return FREEDV_MODE.signalling
|
||||
|
||||
def make_modem_queue_item(self, mode, repeat, repeat_delay, frame):
|
||||
return {
|
||||
|
@ -52,3 +48,9 @@ class TxCommand():
|
|||
self.emit_event(event_queue)
|
||||
self.logger.info(self.log_message())
|
||||
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
|
||||
|
|
|
@ -12,27 +12,33 @@ TESTMODE = False
|
|||
|
||||
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_fmax = config['MODEM']['tuning_range_fmax']
|
||||
self.enable_fsk = config['MODEM']['enable_fsk']
|
||||
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.is_codec2_traffic_counter = 0
|
||||
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.modem_received_queue = modem_rx_q
|
||||
|
@ -41,113 +47,27 @@ class Demodulator():
|
|||
self.states = states
|
||||
self.event_manager = event_manager
|
||||
|
||||
self.fft_queue = fft_queue
|
||||
|
||||
# init codec2 resampler
|
||||
self.resampler = codec2.resampler()
|
||||
|
||||
self.init_state_buffers()
|
||||
self.init_codec2()
|
||||
|
||||
def init_state_buffers(self):
|
||||
# state buffer
|
||||
self.SIG0_DATAC13_STATE = []
|
||||
self.SIG1_DATAC13_STATE = []
|
||||
self.DAT0_DATAC1_STATE = []
|
||||
self.DAT0_DATAC3_STATE = []
|
||||
self.DAT0_DATAC4_STATE = []
|
||||
# enable decoding of signalling modes
|
||||
self.MODE_DICT[codec2.FREEDV_MODE.signalling.value]["decode"] = True
|
||||
|
||||
self.FSK_LDPC0_STATE = []
|
||||
self.FSK_LDPC1_STATE = []
|
||||
|
||||
def init_codec2(self):
|
||||
# Open codec2 instances
|
||||
|
||||
# DATAC13
|
||||
# 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)
|
||||
for mode in codec2.FREEDV_MODE:
|
||||
self.init_codec2_mode(mode.value)
|
||||
|
||||
|
||||
# FSK LDPC - 0
|
||||
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):
|
||||
def init_codec2_mode(self, mode):
|
||||
"""
|
||||
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
|
||||
c2instance = ctypes.cast(
|
||||
|
@ -179,180 +99,60 @@ class Demodulator():
|
|||
nin = codec2.api.freedv_nin(c2instance)
|
||||
|
||||
# Additional Datac0-specific information - these are not referenced anywhere else.
|
||||
# self.sig0_datac0_payload_per_frame = self.sig0_datac0_bytes_per_frame - 2
|
||||
# self.sig0_datac0_n_nom_modem_samples = codec2.api.freedv_get_n_nom_modem_samples(
|
||||
# self.sig0_datac0_freedv
|
||||
# self.signalling_datac0_payload_per_frame = self.signalling_datac0_bytes_per_frame - 2
|
||||
# self.signalling_datac0_n_nom_modem_samples = codec2.api.freedv_get_n_nom_modem_samples(
|
||||
# self.signalling_datac0_freedv
|
||||
# )
|
||||
# self.sig0_datac0_n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(
|
||||
# self.sig0_datac0_freedv
|
||||
# self.signalling_datac0_n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(
|
||||
# self.signalling_datac0_freedv
|
||||
# )
|
||||
# self.sig0_datac0_n_tx_preamble_modem_samples = (
|
||||
# codec2.api.freedv_get_n_tx_preamble_modem_samples(self.sig0_datac0_freedv)
|
||||
# self.signalling_datac0_n_tx_preamble_modem_samples = (
|
||||
# codec2.api.freedv_get_n_tx_preamble_modem_samples(self.signalling_datac0_freedv)
|
||||
# )
|
||||
# self.sig0_datac0_n_tx_postamble_modem_samples = (
|
||||
# codec2.api.freedv_get_n_tx_postamble_modem_samples(self.sig0_datac0_freedv)
|
||||
# self.signalling_datac0_n_tx_postamble_modem_samples = (
|
||||
# codec2.api.freedv_get_n_tx_postamble_modem_samples(self.signalling_datac0_freedv)
|
||||
# )
|
||||
|
||||
# return values
|
||||
return c2instance, bytes_per_frame, bytes_out, audio_buffer, nin
|
||||
self.MODE_DICT[mode]["instance"] = c2instance
|
||||
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):
|
||||
|
||||
self.stream = stream
|
||||
|
||||
for mode in self.MODE_DICT:
|
||||
# Start decoder threads
|
||||
if self.enable_fsk:
|
||||
audio_thread_fsk_ldpc0 = threading.Thread(
|
||||
target=self.audio_fsk_ldpc_0, name="AUDIO_THREAD FSK LDPC0", daemon=True
|
||||
self.MODE_DICT[mode]['decoding_thread'] = threading.Thread(
|
||||
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:
|
||||
x = np.frombuffer(indata, dtype=np.int16)
|
||||
x = self.resampler.resample48_to_8(x)
|
||||
x = audio.set_audio_volume(x, self.rx_audio_level)
|
||||
audio_48k = np.frombuffer(indata, dtype=np.int16)
|
||||
audio_8k = self.resampler.resample48_to_8(audio_48k)
|
||||
audio.calculate_fft(audio_8k, self.fft_queue, self.states)
|
||||
|
||||
# audio recording for debugging purposes
|
||||
# TODO Find a nice place for this
|
||||
#if AudioParam.audio_record:
|
||||
# AudioParam.audio_record_file.writeframes(x)
|
||||
audio_8k_level_adjusted = audio.set_audio_volume(audio_8k, self.rx_audio_level)
|
||||
|
||||
# Avoid decoding when transmitting to reduce CPU
|
||||
# TODO Overriding this for testing purposes
|
||||
# if not self.states.is_transmitting:
|
||||
length_x = len(x)
|
||||
length_audio_8k_level_adjusted = len(audio_8k_level_adjusted)
|
||||
# Avoid buffer overflow by filling only if buffer for
|
||||
# selected datachannel mode is not full
|
||||
for audiobuffer, receive, index in [
|
||||
(self.sig0_datac13_buffer, self.RECEIVE_SIG0, 0),
|
||||
(self.sig1_datac13_buffer, self.RECEIVE_SIG1, 1),
|
||||
(self.dat0_datac1_buffer, self.RECEIVE_DATAC1, 2),
|
||||
(self.dat0_datac3_buffer, self.RECEIVE_DATAC3, 3),
|
||||
(self.dat0_datac4_buffer, self.RECEIVE_DATAC4, 4),
|
||||
(self.fsk_ldpc_buffer_0, self.enable_fsk, 5),
|
||||
(self.fsk_ldpc_buffer_1, self.enable_fsk, 6),
|
||||
]:
|
||||
if (audiobuffer.nbuffer + length_x) > audiobuffer.size:
|
||||
index = 0
|
||||
for mode in self.MODE_DICT:
|
||||
mode_data = self.MODE_DICT[mode]
|
||||
audiobuffer = mode_data['audio_buffer']
|
||||
decode = mode_data['decode']
|
||||
index += 1
|
||||
if audiobuffer:
|
||||
if (audiobuffer.nbuffer + length_audio_8k_level_adjusted) > audiobuffer.size:
|
||||
self.buffer_overflow_counter[index] += 1
|
||||
self.event_manager.send_buffer_overflow(self.buffer_overflow_counter)
|
||||
elif receive:
|
||||
audiobuffer.push(x)
|
||||
return x
|
||||
elif decode:
|
||||
audiobuffer.push(audio_8k_level_adjusted)
|
||||
|
||||
def worker_received(self) -> None:
|
||||
"""Worker for FIFO queue for processing received frames"""
|
||||
|
@ -389,40 +189,20 @@ class Demodulator():
|
|||
offset = round(modemStats.foff) * (-1)
|
||||
return offset
|
||||
|
||||
def demodulate_audio(
|
||||
self,
|
||||
audiobuffer: codec2.audio_buffer,
|
||||
nin: int,
|
||||
freedv: ctypes.c_void_p,
|
||||
bytes_out,
|
||||
bytes_per_frame,
|
||||
state_buffer,
|
||||
mode_name,
|
||||
) -> int:
|
||||
def demodulate_audio(self, mode) -> int:
|
||||
"""
|
||||
De-modulate supplied audio stream with supplied codec2 instance.
|
||||
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
|
||||
#try:
|
||||
audiobuffer = self.MODE_DICT[mode]["audio_buffer"]
|
||||
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:
|
||||
threading.Event().wait(0.01)
|
||||
while audiobuffer.nbuffer >= nin:
|
||||
|
@ -440,7 +220,6 @@ class Demodulator():
|
|||
|
||||
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:
|
||||
self.states.set("is_codec2_traffic", True)
|
||||
self.is_codec2_traffic_counter = self.is_codec2_traffic_cooldown
|
||||
if not self.states.channel_busy:
|
||||
|
@ -469,18 +248,7 @@ class Demodulator():
|
|||
if nbytes == bytes_per_frame:
|
||||
print(bytes(bytes_out))
|
||||
|
||||
# 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
|
||||
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 [
|
||||
if int.from_bytes(bytes(bytes_out[:1]), "big") in [
|
||||
FRAME_TYPE.MESH_BROADCAST.value,
|
||||
FRAME_TYPE.MESH_SIGNALLING_PING.value,
|
||||
FRAME_TYPE.MESH_SIGNALLING_PING_ACK.value,
|
||||
|
@ -499,11 +267,6 @@ class Demodulator():
|
|||
self.get_scatter(freedv)
|
||||
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:
|
||||
"""
|
||||
Callback for TCI RX
|
||||
|
@ -514,61 +277,26 @@ class Demodulator():
|
|||
|
||||
while True:
|
||||
|
||||
x = self.audio_received_queue.get()
|
||||
x = np.frombuffer(x, dtype=np.int16)
|
||||
# x = self.resampler.resample48_to_8(x)
|
||||
audio_48k = self.audio_received_queue.get()
|
||||
audio_48k = np.frombuffer(audio_48k, dtype=np.int16)
|
||||
|
||||
self.calculate_fft(x)
|
||||
audio.calculate_fft(audio_48k, self.fft_queue, self.states)
|
||||
|
||||
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)
|
||||
length_audio_48k = len(audio_48k)
|
||||
index = 0
|
||||
for mode in self.MODE_DICT:
|
||||
mode_data = self.MODE_DICT[mode]
|
||||
audiobuffer = mode_data['audio_buffer']
|
||||
decode = mode_data['decode']
|
||||
index += 1
|
||||
if audiobuffer:
|
||||
if (audiobuffer.nbuffer + length_audio_48k) > audiobuffer.size:
|
||||
self.buffer_overflow_counter[index] += 1
|
||||
self.event_manager.send_buffer_overflow(self.buffer_overflow_counter)
|
||||
elif decode:
|
||||
audiobuffer.push(audio_48k)
|
||||
|
||||
def mkfifo_read_callback(self) -> None:
|
||||
"""
|
||||
Support testing by reading the audio data from a pipe and
|
||||
depositing the data into the codec data buffers.
|
||||
"""
|
||||
while True:
|
||||
threading.Event().wait(0.01)
|
||||
# -----read
|
||||
data_in48k = bytes()
|
||||
with open("", "rb") as fifo:
|
||||
for line in fifo:
|
||||
data_in48k += line
|
||||
|
||||
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:
|
||||
"""
|
||||
|
@ -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_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.fsk_ldpc_freedv_0, frames_per_burst)
|
||||
|
||||
def calculate_snr(self, freedv: ctypes.c_void_p) -> float:
|
||||
"""
|
||||
|
@ -659,13 +386,11 @@ class Demodulator():
|
|||
|
||||
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
|
||||
: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)
|
||||
|
|
|
@ -93,7 +93,6 @@ class DISPATCHER():
|
|||
self.states,
|
||||
self.event_manager,
|
||||
self.modem)
|
||||
|
||||
handler.handle(deconstructed_frame, snr, frequency_offset, freedv, bytes_per_frame)
|
||||
|
||||
def get_id_from_frame(self, data):
|
||||
|
|
|
@ -6,6 +6,8 @@ import structlog
|
|||
import time, uuid
|
||||
from codec2 import FREEDV_MODE
|
||||
|
||||
TESTMODE = False
|
||||
|
||||
class FrameHandler():
|
||||
|
||||
def __init__(self, name: str, config, states: StateManager, event_manager: EventManager,
|
||||
|
@ -87,14 +89,13 @@ class FrameHandler():
|
|||
self.event_manager.broadcast(event_data)
|
||||
|
||||
def get_tx_mode(self):
|
||||
return (
|
||||
FREEDV_MODE.fsk_ldpc_0.value
|
||||
if self.config['MODEM']['enable_fsk']
|
||||
else FREEDV_MODE.sig0.value
|
||||
)
|
||||
return FREEDV_MODE.signalling.value
|
||||
|
||||
def transmit(self, frame):
|
||||
if not TESTMODE:
|
||||
self.modem.transmit(self.get_tx_mode(), 1, 0, frame)
|
||||
else:
|
||||
self.event_manager.broadcast(frame)
|
||||
|
||||
def follow_protocol(self):
|
||||
pass
|
||||
|
|
|
@ -322,9 +322,10 @@ def check_callsign(callsign: str, crc_to_check: bytes, ssid_list):
|
|||
#call_with_ssid.extend(str(ssid).encode("utf-8"))
|
||||
|
||||
callsign_crc = get_crc_24(call_with_ssid)
|
||||
callsign_crc = callsign_crc.hex()
|
||||
|
||||
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 [False, b'']
|
||||
|
|
170
modem/modem.py
170
modem/modem.py
|
@ -14,7 +14,6 @@ import ctypes
|
|||
import queue
|
||||
import threading
|
||||
import time
|
||||
from collections import deque
|
||||
import codec2
|
||||
import numpy as np
|
||||
import sounddevice as sd
|
||||
|
@ -27,6 +26,8 @@ import event_manager
|
|||
import beacon
|
||||
import demodulator
|
||||
|
||||
TESTMODE = False
|
||||
|
||||
class RF:
|
||||
"""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.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.radiocontrol = config['RADIO']['control']
|
||||
|
@ -63,7 +62,6 @@ class RF:
|
|||
self.tci_ip = config['TCI']['tci_ip']
|
||||
self.tci_port = config['TCI']['tci_port']
|
||||
|
||||
self.channel_busy_delay = 0
|
||||
|
||||
self.AUDIO_SAMPLE_RATE = 48000
|
||||
self.MODEM_SAMPLE_RATE = codec2.api.FREEDV_FS_8000
|
||||
|
@ -99,7 +97,9 @@ class RF:
|
|||
self.modem_received_queue,
|
||||
self.data_queue_received,
|
||||
self.states,
|
||||
self.event_manager)
|
||||
self.event_manager,
|
||||
self.fft_queue
|
||||
)
|
||||
|
||||
self.beacon = beacon.Beacon(self.config, self.states, event_queue,
|
||||
self.log, self.modem_transmit_queue)
|
||||
|
@ -110,12 +110,13 @@ class RF:
|
|||
self.tci_module.push_audio(audio_48k)
|
||||
|
||||
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"]:
|
||||
result = self.init_audio()
|
||||
result = self.init_audio() if not TESTMODE else True
|
||||
if not result:
|
||||
raise RuntimeError("Unable to init audio devices")
|
||||
if not TESTMODE:
|
||||
self.demodulator.start(self.sd_input_stream)
|
||||
|
||||
else:
|
||||
|
@ -130,6 +131,7 @@ class RF:
|
|||
|
||||
# init data thread
|
||||
self.init_data_threads()
|
||||
if not TESTMODE:
|
||||
atexit.register(self.sd_input_stream.stop)
|
||||
|
||||
# init beacon
|
||||
|
@ -227,6 +229,7 @@ class RF:
|
|||
daemon=True,
|
||||
)
|
||||
tci_tx_callback_thread.start()
|
||||
return True
|
||||
|
||||
def audio_auto_tune(self):
|
||||
# enable / disable AUDIO TUNE Feature / ALC correction
|
||||
|
@ -269,14 +272,19 @@ class RF:
|
|||
frames:
|
||||
|
||||
"""
|
||||
if TESTMODE:
|
||||
return
|
||||
|
||||
|
||||
self.demodulator.reset_data_sync()
|
||||
# get freedv instance by mode
|
||||
mode_transition = {
|
||||
codec2.FREEDV_MODE.datac0.value: self.freedv_datac0_tx,
|
||||
codec2.FREEDV_MODE.datac1.value: self.freedv_datac1_tx,
|
||||
codec2.FREEDV_MODE.datac3.value: self.freedv_datac3_tx,
|
||||
codec2.FREEDV_MODE.datac4.value: self.freedv_datac4_tx,
|
||||
codec2.FREEDV_MODE.datac13.value: self.freedv_datac13_tx,
|
||||
codec2.FREEDV_MODE.signalling: self.freedv_datac13_tx,
|
||||
codec2.FREEDV_MODE.datac0: self.freedv_datac0_tx,
|
||||
codec2.FREEDV_MODE.datac1: self.freedv_datac1_tx,
|
||||
codec2.FREEDV_MODE.datac3: self.freedv_datac3_tx,
|
||||
codec2.FREEDV_MODE.datac4: self.freedv_datac4_tx,
|
||||
codec2.FREEDV_MODE.datac13: self.freedv_datac13_tx,
|
||||
}
|
||||
if mode in mode_transition:
|
||||
freedv = mode_transition[mode]
|
||||
|
@ -341,13 +349,7 @@ class RF:
|
|||
|
||||
# Create modulation for all frames in the list
|
||||
for frame in frames:
|
||||
# Write preamble to txbuffer
|
||||
# codec2 fsk preamble may be broken -
|
||||
# at least it sounds like that, so we are disabling it for testing
|
||||
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)
|
||||
|
@ -375,12 +377,6 @@ class RF:
|
|||
codec2.api.freedv_rawdatatx(freedv, mod_out, data)
|
||||
txbuffer += bytes(mod_out)
|
||||
|
||||
# codec2 fsk postamble may be broken -
|
||||
# at least it sounds like that, so we are disabling it for testing
|
||||
if self.MODE not in [
|
||||
codec2.FREEDV_MODE.fsk_ldpc_0.value,
|
||||
codec2.FREEDV_MODE.fsk_ldpc_1.value,
|
||||
]:
|
||||
# Write postamble to txbuffer
|
||||
codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble)
|
||||
# Append postamble to txbuffer
|
||||
|
@ -451,6 +447,7 @@ class RF:
|
|||
end_of_transmission = time.time()
|
||||
transmission_time = end_of_transmission - start_of_transmission
|
||||
self.log.debug("[MDM] ON AIR TIME", time=transmission_time)
|
||||
return True
|
||||
|
||||
def transmit_morse(self, repeats, repeat_delay, frames):
|
||||
self.states.waitForTransmission()
|
||||
|
@ -516,8 +513,6 @@ class RF:
|
|||
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_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):
|
||||
worker_received = threading.Thread(
|
||||
|
@ -529,7 +524,6 @@ class RF:
|
|||
def transmit_audio(self, audio_48k) -> None:
|
||||
self.radio.set_ptt(True)
|
||||
self.event_manager.send_ptt_change(True)
|
||||
self.calculate_fft(audio_48k)
|
||||
|
||||
if self.radiocontrol in ["tci"]:
|
||||
self.tci_tx_callback(audio_48k)
|
||||
|
@ -606,123 +600,3 @@ class RF:
|
|||
)
|
||||
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
|
||||
|
||||
|
|
|
@ -240,9 +240,6 @@ def sock_events(sock):
|
|||
|
||||
@sock.route('/fft')
|
||||
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)
|
||||
|
||||
@sock.route('/states')
|
||||
|
|
|
@ -17,7 +17,6 @@ class SM:
|
|||
self.config = self.app.config_manager.read()
|
||||
self.modem_events = app.modem_events
|
||||
self.modem_fft = app.modem_fft
|
||||
self.enable_fft_stream = False
|
||||
self.modem_service = app.modem_service
|
||||
self.states = app.state_manager
|
||||
|
||||
|
@ -49,14 +48,6 @@ class SM:
|
|||
threading.Event().wait(0.5)
|
||||
if self.start_modem():
|
||||
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:
|
||||
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.states.set("is_modem_running", True)
|
||||
self.modem.set_FFT_stream(self.enable_fft_stream)
|
||||
self.modem.start_modem()
|
||||
|
||||
return True
|
||||
|
|
|
@ -61,8 +61,8 @@ class TestDataFrameFactory(unittest.TestCase):
|
|||
FREEDV_MODE.datac3, session_id, offset, payload)
|
||||
|
||||
def testAvailablePayload(self):
|
||||
avail = self.factory.get_available_data_payload_for_mode(FRAME_TYPE.BURST_FRAME, FREEDV_MODE.datac3)
|
||||
self.assertEqual(avail, 123) # 128 bytes datac3 frame payload - BURST frame overhead
|
||||
avail = self.factory.get_available_data_payload_for_mode(FRAME_TYPE.ARQ_BURST_FRAME, FREEDV_MODE.datac3)
|
||||
self.assertEqual(avail, 120) # 128 bytes datac3 frame payload - BURST frame overhead
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -9,6 +9,9 @@ import queue
|
|||
from state_manager import StateManager
|
||||
from command_ping import PingCommand
|
||||
from command_cq import CQCommand
|
||||
import modem
|
||||
import frame_handler
|
||||
|
||||
|
||||
class TestProtocols(unittest.TestCase):
|
||||
|
||||
|
@ -22,18 +25,19 @@ class TestProtocols(unittest.TestCase):
|
|||
|
||||
cls.event_queue = queue.Queue()
|
||||
|
||||
cls.data_queue_received = 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.event_queue,
|
||||
cls.state_manager,
|
||||
cls.data_queue_received,
|
||||
cls.modem_transmit_queue)
|
||||
cls.modem)
|
||||
|
||||
def shortcutTransmission(self):
|
||||
transmission_item = self.modem_transmit_queue.get()
|
||||
frame_bytes = bytes(transmission_item['frame'])
|
||||
def shortcutTransmission(self, frame_bytes):
|
||||
self.frame_dispatcher.new_process_data(frame_bytes, None, len(frame_bytes), 0, 0)
|
||||
|
||||
def assertEventReceivedType(self, event_type):
|
||||
|
@ -44,30 +48,36 @@ class TestProtocols(unittest.TestCase):
|
|||
|
||||
def testPingWithAck(self):
|
||||
# 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.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
|
||||
self.shortcutTransmission()
|
||||
self.shortcutTransmission(frame)
|
||||
self.assertEventReceivedType('PING')
|
||||
|
||||
event_frame = self.event_queue.get()
|
||||
# Check ACK
|
||||
self.shortcutTransmission()
|
||||
self.shortcutTransmission(event_frame)
|
||||
self.assertEventReceivedType('PING_ACK')
|
||||
print("PING/PING ACK CHECK SUCCESSFULLY")
|
||||
|
||||
def testCQWithQRV(self):
|
||||
self.config['MODEM']['respond_to_cq'] = True
|
||||
|
||||
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.shortcutTransmission()
|
||||
event_frame = self.event_queue.get()
|
||||
# Check QRV
|
||||
self.shortcutTransmission(event_frame)
|
||||
self.assertEventReceivedType('QRV')
|
||||
print("CQ/QRV CHECK SUCCESSFULLY")
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
Loading…
Reference in a new issue