first stab at ft8 decoding: chop up audio, call jt9 binary to decode
This commit is contained in:
parent
3f05565b7b
commit
284646ee6c
19
csdr.py
19
csdr.py
@ -21,11 +21,11 @@ OpenWebRX csdr plugin: do the signal processing with csdr
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import signal
|
||||
import threading
|
||||
from functools import partial
|
||||
from owrx.wsjt import Ft8Chopper
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -186,6 +186,12 @@ class dsp(object):
|
||||
"csdr timing_recovery_cc GARDNER {secondary_samples_per_bits} 0.5 2 --add_q | " + \
|
||||
"CSDR_FIXED_BUFSIZE=1 csdr dbpsk_decoder_c_u8 | " + \
|
||||
"CSDR_FIXED_BUFSIZE=1 csdr psk31_varicode_decoder_u8_u8"
|
||||
elif which == "ft8":
|
||||
chain = secondary_chain_base + "csdr realpart_cf | "
|
||||
if self.last_decimation != 1.0 :
|
||||
chain += "csdr fractional_decimator_ff {last_decimation} | "
|
||||
chain += "csdr agc_ff | csdr limit_ff | csdr convert_f_s16"
|
||||
return chain
|
||||
|
||||
def set_secondary_demodulator(self, what):
|
||||
if self.get_secondary_demodulator() == what:
|
||||
@ -238,7 +244,8 @@ class dsp(object):
|
||||
secondary_samples_per_bits=self.secondary_samples_per_bits(),
|
||||
secondary_bpf_cutoff=self.secondary_bpf_cutoff(),
|
||||
secondary_bpf_transition_bw=self.secondary_bpf_transition_bw(),
|
||||
if_samp_rate=self.if_samp_rate()
|
||||
if_samp_rate=self.if_samp_rate(),
|
||||
last_decimation=self.last_decimation
|
||||
)
|
||||
|
||||
logger.debug("[openwebrx-dsp-plugin:csdr] secondary command (fft) = %s", secondary_command_fft)
|
||||
@ -253,6 +260,10 @@ class dsp(object):
|
||||
self.secondary_processes_running = True
|
||||
|
||||
self.output.add_output("secondary_fft", partial(self.secondary_process_fft.stdout.read, int(self.get_secondary_fft_bytes_to_read())))
|
||||
if self.get_secondary_demodulator() == "ft8":
|
||||
chopper = Ft8Chopper(self.secondary_process_demod.stdout)
|
||||
chopper.start()
|
||||
else:
|
||||
self.output.add_output("secondary_demod", partial(self.secondary_process_demod.stdout.read, 1))
|
||||
|
||||
#open control pipes for csdr and send initialization data
|
||||
@ -262,7 +273,7 @@ class dsp(object):
|
||||
|
||||
def set_secondary_offset_freq(self, value):
|
||||
self.secondary_offset_freq=value
|
||||
if self.secondary_processes_running:
|
||||
if self.secondary_processes_running and hasattr(self, "secondary_shift_pipe_file"):
|
||||
self.secondary_shift_pipe_file.write("%g\n"%(-float(self.secondary_offset_freq)/self.if_samp_rate()))
|
||||
self.secondary_shift_pipe_file.flush()
|
||||
|
||||
@ -332,6 +343,8 @@ class dsp(object):
|
||||
def get_audio_rate(self):
|
||||
if self.isDigitalVoice():
|
||||
return 48000
|
||||
elif self.secondary_demodulator == "ft8":
|
||||
return 12000
|
||||
return self.get_output_rate()
|
||||
|
||||
def isDigitalVoice(self, demodulator = None):
|
||||
|
@ -109,6 +109,7 @@
|
||||
<select id="openwebrx-secondary-demod-listbox" onchange="secondary_demod_listbox_changed();">
|
||||
<option value="none"></option>
|
||||
<option value="bpsk31">BPSK31</option>
|
||||
<option value="ft8">FT8</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="openwebrx-panel-line">
|
||||
|
@ -2633,6 +2633,7 @@ function demodulator_digital_replace(subtype)
|
||||
{
|
||||
case "bpsk31":
|
||||
case "rtty":
|
||||
case "ft8":
|
||||
secondary_demod_start(subtype);
|
||||
demodulator_analog_replace('usb', true);
|
||||
demodulator_buttons_update();
|
||||
@ -2809,6 +2810,9 @@ function secondary_demod_listbox_changed()
|
||||
case "rtty":
|
||||
demodulator_digital_replace('rtty');
|
||||
break;
|
||||
case "ft8":
|
||||
demodulator_digital_replace('ft8');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
79
owrx/wsjt.py
Normal file
79
owrx/wsjt.py
Normal file
@ -0,0 +1,79 @@
|
||||
import threading
|
||||
import wave
|
||||
from datetime import datetime, timedelta
|
||||
import time
|
||||
import sched
|
||||
import subprocess
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Ft8Chopper(threading.Thread):
|
||||
def __init__(self, source):
|
||||
self.source = source
|
||||
(self.wavefilename, self.wavefile) = self.getWaveFile()
|
||||
self.scheduler = sched.scheduler(time.time, time.sleep)
|
||||
self.queue = []
|
||||
self.doRun = True
|
||||
super().__init__()
|
||||
|
||||
def getWaveFile(self):
|
||||
filename = "/tmp/openwebrx-ft8chopper-{0}.wav".format(datetime.now().strftime("%Y%m%d-%H%M%S"))
|
||||
wavefile = wave.open(filename, "wb")
|
||||
wavefile.setnchannels(1)
|
||||
wavefile.setsampwidth(2)
|
||||
wavefile.setframerate(12000)
|
||||
return (filename, wavefile)
|
||||
|
||||
def getNextDecodingTime(self):
|
||||
t = datetime.now()
|
||||
seconds = (int(t.second / 15) + 1) * 15
|
||||
if seconds >= 60:
|
||||
t = t + timedelta(minutes = 1)
|
||||
seconds = 0
|
||||
t = t.replace(second = seconds, microsecond = 0)
|
||||
logger.debug("scheduling: {0}".format(t))
|
||||
return t.timestamp()
|
||||
|
||||
def startScheduler(self):
|
||||
self._scheduleNextSwitch()
|
||||
threading.Thread(target = self.scheduler.run).start()
|
||||
|
||||
def emptyScheduler(self):
|
||||
for event in self.scheduler.queue:
|
||||
self.scheduler.cancel(event)
|
||||
|
||||
def _scheduleNextSwitch(self):
|
||||
self.scheduler.enterabs(self.getNextDecodingTime(), 1, self.switchFiles)
|
||||
|
||||
def switchFiles(self):
|
||||
file = self.wavefile
|
||||
filename = self.wavefilename
|
||||
(self.wavefilename, self.wavefile) = self.getWaveFile()
|
||||
|
||||
file.close()
|
||||
self.queue.append(filename)
|
||||
self._scheduleNextSwitch()
|
||||
|
||||
def decode(self):
|
||||
if self.queue:
|
||||
file = self.queue.pop()
|
||||
logger.debug("processing file {0}".format(file))
|
||||
#TODO expose decoding quality parameters through config
|
||||
self.decoder = subprocess.Popen(["jt9", "--ft8", "-d", "3", file])
|
||||
|
||||
def run(self) -> None:
|
||||
logger.debug("FT8 chopper starting up")
|
||||
self.startScheduler()
|
||||
while self.doRun:
|
||||
data = self.source.read(256)
|
||||
if data is None or (isinstance(data, bytes) and len(data) == 0):
|
||||
logger.warning("zero read on ft8 chopper")
|
||||
self.doRun = False
|
||||
else:
|
||||
self.wavefile.writeframes(data)
|
||||
|
||||
self.decode()
|
||||
logger.debug("FT8 chopper shutting down")
|
||||
self.emptyScheduler()
|
Loading…
Reference in New Issue
Block a user