mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
some modem related work
This commit is contained in:
parent
98f1f3c5ca
commit
43fdd83178
2 changed files with 94 additions and 146 deletions
218
modem/modem.py
218
modem/modem.py
|
@ -70,25 +70,15 @@ class RF:
|
||||||
# 8 * (self.AUDIO_SAMPLE_RATE/self.MODEM_SAMPLE_RATE) == 48
|
# 8 * (self.AUDIO_SAMPLE_RATE/self.MODEM_SAMPLE_RATE) == 48
|
||||||
self.AUDIO_CHANNELS = 1
|
self.AUDIO_CHANNELS = 1
|
||||||
self.MODE = 0
|
self.MODE = 0
|
||||||
|
|
||||||
# Locking state for mod out so buffer will be filled before we can use it
|
|
||||||
# https://github.com/DJ2LS/FreeDATA/issues/127
|
|
||||||
# https://github.com/DJ2LS/FreeDATA/issues/99
|
|
||||||
self.mod_out_locked = True
|
|
||||||
self.rms_counter = 0
|
self.rms_counter = 0
|
||||||
|
|
||||||
# Make sure our resampler will work
|
# Make sure our resampler will work
|
||||||
assert (self.AUDIO_SAMPLE_RATE / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 # type: ignore
|
assert (self.AUDIO_SAMPLE_RATE / self.MODEM_SAMPLE_RATE) == codec2.api.FDMDV_OS_48 # type: ignore
|
||||||
|
|
||||||
self.modem_transmit_queue = queue.Queue()
|
|
||||||
self.modem_received_queue = queue.Queue()
|
self.modem_received_queue = queue.Queue()
|
||||||
|
|
||||||
self.audio_received_queue = queue.Queue()
|
self.audio_received_queue = queue.Queue()
|
||||||
|
|
||||||
self.data_queue_received = queue.Queue()
|
self.data_queue_received = queue.Queue()
|
||||||
|
|
||||||
self.event_manager = event_manager.EventManager([event_queue])
|
self.event_manager = event_manager.EventManager([event_queue])
|
||||||
|
|
||||||
self.fft_queue = fft_queue
|
self.fft_queue = fft_queue
|
||||||
|
|
||||||
self.demodulator = demodulator.Demodulator(self.config,
|
self.demodulator = demodulator.Demodulator(self.config,
|
||||||
|
@ -280,38 +270,14 @@ class RF:
|
||||||
# Open codec2 instance
|
# Open codec2 instance
|
||||||
self.MODE = mode
|
self.MODE = mode
|
||||||
|
|
||||||
# Get number of bytes per frame for mode
|
txbuffer = bytes()
|
||||||
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
|
|
||||||
payload_bytes_per_frame = bytes_per_frame - 2
|
|
||||||
|
|
||||||
# Init buffer for data
|
|
||||||
n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv)
|
|
||||||
mod_out = ctypes.create_string_buffer(n_tx_modem_samples * 2)
|
|
||||||
|
|
||||||
# Init buffer for preample
|
|
||||||
n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(
|
|
||||||
freedv
|
|
||||||
)
|
|
||||||
mod_out_preamble = ctypes.create_string_buffer(n_tx_preamble_modem_samples * 2)
|
|
||||||
|
|
||||||
# Init buffer for postamble
|
|
||||||
n_tx_postamble_modem_samples = (
|
|
||||||
codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv)
|
|
||||||
)
|
|
||||||
mod_out_postamble = ctypes.create_string_buffer(
|
|
||||||
n_tx_postamble_modem_samples * 2
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add empty data to handle ptt toggle time
|
# Add empty data to handle ptt toggle time
|
||||||
if self.tx_delay > 0:
|
if self.tx_delay > 0:
|
||||||
data_delay = int(self.MODEM_SAMPLE_RATE * (self.tx_delay / 1000)) # type: ignore
|
self.transmit_add_silence(txbuffer, self.tx_delay)
|
||||||
mod_out_silence = ctypes.create_string_buffer(data_delay * 2)
|
|
||||||
txbuffer = bytes(mod_out_silence)
|
|
||||||
else:
|
|
||||||
txbuffer = bytes()
|
|
||||||
|
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
"[MDM] TRANSMIT", mode=self.MODE, payload=payload_bytes_per_frame, delay=self.tx_delay
|
"[MDM] TRANSMIT", mode=self.MODE, delay=self.tx_delay
|
||||||
)
|
)
|
||||||
|
|
||||||
if not isinstance(frames, list): frames = [frames]
|
if not isinstance(frames, list): frames = [frames]
|
||||||
|
@ -320,9 +286,78 @@ 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:
|
||||||
|
|
||||||
|
txbuffer = self.transmit_add_preamble(txbuffer, freedv)
|
||||||
|
txbuffer = self.transmit_create_frame(txbuffer, freedv, frame)
|
||||||
|
txbuffer = self.transmit_add_postamble(txbuffer, freedv)
|
||||||
|
|
||||||
|
# Add delay to end of frames
|
||||||
|
self.transmit_add_silence(txbuffer, repeat_delay)
|
||||||
|
|
||||||
|
# Re-sample back up to 48k (resampler works on np.int16)
|
||||||
|
x = np.frombuffer(txbuffer, dtype=np.int16)
|
||||||
|
|
||||||
|
self.audio_auto_tune()
|
||||||
|
x = audio.set_audio_volume(x, self.tx_audio_level)
|
||||||
|
|
||||||
|
if self.radiocontrol not in ["tci"]:
|
||||||
|
txbuffer_out = self.resampler.resample8_to_48(x)
|
||||||
|
else:
|
||||||
|
txbuffer_out = x
|
||||||
|
|
||||||
|
# transmit audio
|
||||||
|
self.transmit_audio(txbuffer_out)
|
||||||
|
|
||||||
|
self.states.set("channel_busy", False)
|
||||||
|
self.radio.set_ptt(False)
|
||||||
|
self.event_manager.send_ptt_change(False)
|
||||||
|
self.states.setTransmitting(False)
|
||||||
|
|
||||||
|
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_add_preamble(self, buffer, freedv):
|
||||||
|
|
||||||
|
# Init buffer for preample
|
||||||
|
n_tx_preamble_modem_samples = codec2.api.freedv_get_n_tx_preamble_modem_samples(
|
||||||
|
freedv
|
||||||
|
)
|
||||||
|
mod_out_preamble = ctypes.create_string_buffer(n_tx_preamble_modem_samples * 2)
|
||||||
|
|
||||||
# Write preamble to txbuffer
|
# Write preamble to txbuffer
|
||||||
codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble)
|
codec2.api.freedv_rawdatapreambletx(freedv, mod_out_preamble)
|
||||||
txbuffer += bytes(mod_out_preamble)
|
buffer += bytes(mod_out_preamble)
|
||||||
|
return buffer
|
||||||
|
|
||||||
|
def transmit_add_postamble(self, buffer, freedv):
|
||||||
|
# Init buffer for postamble
|
||||||
|
n_tx_postamble_modem_samples = (
|
||||||
|
codec2.api.freedv_get_n_tx_postamble_modem_samples(freedv)
|
||||||
|
)
|
||||||
|
mod_out_postamble = ctypes.create_string_buffer(
|
||||||
|
n_tx_postamble_modem_samples * 2
|
||||||
|
)
|
||||||
|
# Write postamble to txbuffer
|
||||||
|
codec2.api.freedv_rawdatapostambletx(freedv, mod_out_postamble)
|
||||||
|
# Append postamble to txbuffer
|
||||||
|
buffer += bytes(mod_out_postamble)
|
||||||
|
return buffer
|
||||||
|
|
||||||
|
def transmit_add_silence(self, buffer, duration):
|
||||||
|
data_delay = int(self.MODEM_SAMPLE_RATE * (duration / 1000)) # type: ignore
|
||||||
|
mod_out_silence = ctypes.create_string_buffer(data_delay * 2)
|
||||||
|
buffer += bytes(mod_out_silence)
|
||||||
|
return buffer
|
||||||
|
|
||||||
|
def transmit_create_frame(self, txbuffer, freedv, frame):
|
||||||
|
# Get number of bytes per frame for mode
|
||||||
|
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
|
||||||
|
payload_bytes_per_frame = bytes_per_frame - 2
|
||||||
|
|
||||||
|
# Init buffer for data
|
||||||
|
n_tx_modem_samples = codec2.api.freedv_get_n_tx_modem_samples(freedv)
|
||||||
|
mod_out = ctypes.create_string_buffer(n_tx_modem_samples * 2)
|
||||||
|
|
||||||
# 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)
|
||||||
|
@ -347,78 +382,7 @@ class RF:
|
||||||
# modulate DATA and save it into mod_out pointer
|
# modulate DATA and save it into mod_out pointer
|
||||||
codec2.api.freedv_rawdatatx(freedv, mod_out, data)
|
codec2.api.freedv_rawdatatx(freedv, mod_out, data)
|
||||||
txbuffer += bytes(mod_out)
|
txbuffer += bytes(mod_out)
|
||||||
|
return txbuffer
|
||||||
# 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
|
|
||||||
samples_delay = int(self.MODEM_SAMPLE_RATE * (repeat_delay / 1000)) # type: ignore
|
|
||||||
mod_out_silence = ctypes.create_string_buffer(samples_delay * 2)
|
|
||||||
txbuffer += bytes(mod_out_silence)
|
|
||||||
|
|
||||||
# Re-sample back up to 48k (resampler works on np.int16)
|
|
||||||
x = np.frombuffer(txbuffer, dtype=np.int16)
|
|
||||||
|
|
||||||
self.audio_auto_tune()
|
|
||||||
x = audio.set_audio_volume(x, self.tx_audio_level)
|
|
||||||
|
|
||||||
if not self.radiocontrol in ["tci"]:
|
|
||||||
txbuffer_out = self.resampler.resample8_to_48(x)
|
|
||||||
else:
|
|
||||||
txbuffer_out = x
|
|
||||||
|
|
||||||
# Explicitly lock our usage of mod_out_queue if needed
|
|
||||||
# This could avoid audio problems on slower CPU
|
|
||||||
# we will fill our modout list with all data, then start
|
|
||||||
# processing it in audio callback
|
|
||||||
self.mod_out_locked = True
|
|
||||||
|
|
||||||
# -------------------------------
|
|
||||||
# add modulation to modout_queue
|
|
||||||
self.transmit_audio(txbuffer_out)
|
|
||||||
|
|
||||||
# Release our mod_out_lock, so we can use the queue
|
|
||||||
self.mod_out_locked = False
|
|
||||||
|
|
||||||
# we need to wait manually for tci processing
|
|
||||||
if self.radiocontrol in ["tci"]:
|
|
||||||
duration = len(txbuffer_out) / 8000
|
|
||||||
timestamp_to_sleep = time.time() + duration
|
|
||||||
self.log.debug("[MDM] TCI calculated duration", duration=duration)
|
|
||||||
tci_timeout_reached = False
|
|
||||||
#while time.time() < timestamp_to_sleep:
|
|
||||||
# threading.Event().wait(0.01)
|
|
||||||
else:
|
|
||||||
timestamp_to_sleep = time.time()
|
|
||||||
# set tci timeout reached to True for overriding if not used
|
|
||||||
tci_timeout_reached = True
|
|
||||||
|
|
||||||
while not tci_timeout_reached:
|
|
||||||
if self.radiocontrol in ["tci"]:
|
|
||||||
if time.time() < timestamp_to_sleep:
|
|
||||||
tci_timeout_reached = False
|
|
||||||
else:
|
|
||||||
tci_timeout_reached = True
|
|
||||||
threading.Event().wait(0.01)
|
|
||||||
# if we're transmitting FreeDATA signals, reset channel busy state
|
|
||||||
self.states.set("channel_busy", False)
|
|
||||||
|
|
||||||
self.radio.set_ptt(False)
|
|
||||||
|
|
||||||
# Push ptt state to socket stream
|
|
||||||
self.event_manager.send_ptt_change(False)
|
|
||||||
|
|
||||||
# After processing, set the locking state back to true to be prepared for next transmission
|
|
||||||
self.mod_out_locked = True
|
|
||||||
|
|
||||||
self.states.setTransmitting(False)
|
|
||||||
|
|
||||||
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):
|
def transmit_morse(self, repeats, repeat_delay, frames):
|
||||||
self.states.waitForTransmission()
|
self.states.waitForTransmission()
|
||||||
|
@ -432,43 +396,11 @@ class RF:
|
||||||
|
|
||||||
txbuffer_out = cw.MorseCodePlayer().text_to_signal("DJ2LS-1")
|
txbuffer_out = cw.MorseCodePlayer().text_to_signal("DJ2LS-1")
|
||||||
|
|
||||||
self.mod_out_locked = True
|
|
||||||
self.transmit_audio(txbuffer_out)
|
self.transmit_audio(txbuffer_out)
|
||||||
self.mod_out_locked = False
|
|
||||||
|
|
||||||
# we need to wait manually for tci processing
|
|
||||||
if self.radiocontrol in ["tci"]:
|
|
||||||
duration = len(txbuffer_out) / 8000
|
|
||||||
timestamp_to_sleep = time.time() + duration
|
|
||||||
self.log.debug("[MDM] TCI calculated duration", duration=duration)
|
|
||||||
tci_timeout_reached = False
|
|
||||||
#while time.time() < timestamp_to_sleep:
|
|
||||||
# threading.Event().wait(0.01)
|
|
||||||
else:
|
|
||||||
timestamp_to_sleep = time.time()
|
|
||||||
# set tci timeout reached to True for overriding if not used
|
|
||||||
tci_timeout_reached = True
|
|
||||||
|
|
||||||
while not tci_timeout_reached:
|
|
||||||
if self.radiocontrol in ["tci"]:
|
|
||||||
if time.time() < timestamp_to_sleep:
|
|
||||||
tci_timeout_reached = False
|
|
||||||
else:
|
|
||||||
tci_timeout_reached = True
|
|
||||||
|
|
||||||
threading.Event().wait(0.01)
|
|
||||||
# if we're transmitting FreeDATA signals, reset channel busy state
|
|
||||||
self.states.set("channel_busy", False)
|
self.states.set("channel_busy", False)
|
||||||
|
|
||||||
self.radio.set_ptt(False)
|
self.radio.set_ptt(False)
|
||||||
|
|
||||||
# Push ptt state to socket stream
|
|
||||||
self.event_manager.send_ptt_change(False)
|
self.event_manager.send_ptt_change(False)
|
||||||
|
|
||||||
# After processing, set the locking state back to true to be prepared for next transmission
|
|
||||||
self.mod_out_locked = True
|
|
||||||
|
|
||||||
self.modem_transmit_queue.task_done()
|
|
||||||
self.states.setTransmitting(False)
|
self.states.setTransmitting(False)
|
||||||
|
|
||||||
end_of_transmission = time.time()
|
end_of_transmission = time.time()
|
||||||
|
@ -498,6 +430,8 @@ class RF:
|
||||||
|
|
||||||
if self.radiocontrol in ["tci"]:
|
if self.radiocontrol in ["tci"]:
|
||||||
self.tci_tx_callback(audio_48k)
|
self.tci_tx_callback(audio_48k)
|
||||||
|
# we need to wait manually for tci processing
|
||||||
|
self.tci_module.wait_until_transmitted(audio_48k)
|
||||||
else:
|
else:
|
||||||
sd.play(audio_48k, blocking=True)
|
sd.play(audio_48k, blocking=True)
|
||||||
return
|
return
|
||||||
|
|
14
modem/tci.py
14
modem/tci.py
|
@ -315,3 +315,17 @@ class TCICtrl:
|
||||||
def close_rig(self):
|
def close_rig(self):
|
||||||
""" """
|
""" """
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def wait_until_transmitted(self, txbuffer_out):
|
||||||
|
duration = len(txbuffer_out) / 8000
|
||||||
|
timestamp_to_sleep = time.time() + duration
|
||||||
|
self.log.debug("[MDM] TCI calculated duration", duration=duration)
|
||||||
|
tci_timeout_reached = False
|
||||||
|
while not tci_timeout_reached:
|
||||||
|
if self.radiocontrol in ["tci"]:
|
||||||
|
if time.time() < timestamp_to_sleep:
|
||||||
|
tci_timeout_reached = False
|
||||||
|
else:
|
||||||
|
tci_timeout_reached = True
|
||||||
|
threading.Event().wait(0.01)
|
||||||
|
# if we're transmitting FreeDATA signals, reset channel busy state
|
Loading…
Reference in a new issue