JT4 enablement

This commit is contained in:
Dawid SQ6EMM 2024-02-28 23:21:11 +01:00
parent 10c642e102
commit c16ec8ef0d
12 changed files with 49 additions and 10 deletions

View file

@ -3,6 +3,7 @@
- Added support for the MSK144 digimode
- Added support for decoding ADS-B with dump1090
- Added support for decoding HFDL and VDL2 aircraft communications
- Added support for JT4 modes
- Added decoding of ISM band transmissions using rtl_433
- Added support for decoding RDS data on WFM broadcasts using redsea decoder
- Added decoding for DAB broadcast stations using csdr-eti and dablin

View file

@ -15,7 +15,7 @@ It has the following features:
- Multiple SDR devices can be used simultaneously
- [digiham](https://github.com/jketterl/digiham) based demodularors (DMR, YSF, Pocsag, D-Star, NXDN)
- [wsjt-x](https://wsjt.sourceforge.io/) based demodulators (FT8, FT4, WSPR, JT65, JT9, FST4,
FST4W)
FST4W, JT4)
- [direwolf](https://github.com/wb2osz/direwolf) based demodulation of APRS packets
- [JS8Call](http://js8call.com/) support
- [DRM](https://github.com/jketterl/openwebrx/wiki/DRM-demodulator-notes) support

View file

@ -94,4 +94,4 @@ case ${1:-} in
*)
usage
;;
esac
esac

View file

@ -167,7 +167,7 @@ DemodulatorPanel.prototype.updatePanels = function() {
var mode = Modes.findByModulation(modulation);
toggle_panel("openwebrx-panel-digimodes", modulation && (!mode || mode.secondaryFft));
// WSJT-X modes share the same panel
toggle_panel("openwebrx-panel-wsjt-message", ['ft8', 'wspr', 'jt65', 'jt9', 'ft4', 'fst4', 'fst4w', "q65", "msk144"].indexOf(modulation) >= 0);
toggle_panel("openwebrx-panel-wsjt-message", ['ft8', 'wspr', 'jt65', 'jt9', 'jt4', 'ft4', 'fst4', 'fst4w', "q65", "msk144"].indexOf(modulation) >= 0);
// these modes come with their own
['js8', 'packet', 'pocsag', 'adsb', 'ism', 'hfdl', 'vdl2'].forEach(function(m) {
toggle_panel('openwebrx-panel-' + m + '-message', modulation === m);
@ -382,4 +382,4 @@ $.fn.demodulatorPanel = function(){
this.data('panel', new DemodulatorPanel(this));
}
return this.data('panel');
};
};

View file

@ -59,7 +59,7 @@ MessagePanel.prototype.scrollToBottom = function() {
function WsjtMessagePanel(el) {
MessagePanel.call(this, el);
this.initClearTimer();
this.qsoModes = ['FT8', 'JT65', 'JT9', 'FT4', 'FST4', 'Q65', 'MSK144'];
this.qsoModes = ['FT8', 'JT65', 'JT9', 'JT4', 'FT4', 'FST4', 'Q65', 'MSK144'];
this.beaconModes = ['WSPR', 'FST4W'];
this.modes = [].concat(this.qsoModes, this.beaconModes);
}
@ -809,4 +809,4 @@ $.fn.vdl2MessagePanel = function() {
this.data('panel', new Vdl2MessagePanel(this));
}
return this.data('panel');
};
};

View file

@ -155,6 +155,7 @@ defaultConfig = PropertyLayer(
wsjt_decoding_depths=PropertyLayer(jt65=1),
fst4_enabled_intervals=[15, 30],
fst4w_enabled_intervals=[120, 300],
jt4_enabled_submodes=["F", "G"],
q65_enabled_combinations=["A30", "E120", "C60"],
js8_enabled_profiles=["normal", "slow"],
js8_decoding_depth=3,

View file

@ -4,7 +4,7 @@ from owrx.form.input import CheckboxInput, NumberInput, DropdownInput, Js8Profil
from owrx.form.input.wfm import WfmTauValues
from owrx.form.input.wsjt import Q65ModeMatrix, WsjtDecodingDepthsInput
from owrx.form.input.converter import OptionalConverter
from owrx.wsjt import Fst4Profile, Fst4wProfile
from owrx.wsjt import Fst4Profile, Fst4wProfile, JT4Profile
from owrx.breadcrumb import Breadcrumb, BreadcrumbItem
@ -90,6 +90,11 @@ class DecodingSettingsController(SettingsFormController):
"Enabled FST4W intervals",
[Option(v, "{}s".format(v)) for v in Fst4wProfile.availableIntervals],
),
MultiCheckboxInput(
"jt4_enabled_submodes",
"Enabled JT4 Submodes",
[Option(v, "{}".format(v)) for v in JT4Profile.availableSubmodes],
),
Q65ModeMatrix("q65_enabled_combinations", "Enabled Q65 Mode combinations"),
),
]

View file

@ -611,7 +611,7 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient)
def _getSecondaryDemodulator(self, mod) -> Optional[SecondaryDemodulator]:
if isinstance(mod, SecondaryDemodulator):
return mod
if mod in ["ft8", "wspr", "jt65", "jt9", "ft4", "fst4", "fst4w", "q65"]:
if mod in ["ft8", "wspr", "jt65", "jt9", "jt4", "ft4", "fst4", "fst4w", "q65"]:
from csdr.chain.digimodes import AudioChopperDemodulator
from owrx.wsjt import WsjtParser
return AudioChopperDemodulator(mod, WsjtParser())

View file

@ -147,6 +147,7 @@ class Modes(object):
WsjtMode("wspr", "WSPR", bandpass=Bandpass(1350, 1650)),
WsjtMode("fst4", "FST4", requirements=["wsjt-x-2-3"]),
WsjtMode("fst4w", "FST4W", bandpass=Bandpass(1350, 1650), requirements=["wsjt-x-2-3"]),
WsjtMode("jt4", "JT4", requirements=["wsjt-x-2-4"]),
WsjtMode("q65", "Q65", requirements=["wsjt-x-2-4"]),
DigitalMode("msk144", "MSK144", requirements=["msk144"], underlying=["usb"], service=True),
Js8Mode("js8", "JS8Call"),

View file

@ -29,7 +29,7 @@ class PskReporter(FilteredReporter):
Current version at the time of the last change:
https://www.adif.org/314/ADIF_314.htm#Mode_Enumeration
"""
return ["FT8", "FT4", "JT9", "JT65", "FST4", "JS8", "Q65", "WSPR", "FST4W", "MSK144"]
return ["FT8", "FT4", "JT9", "JT4", "JT65", "FST4", "JS8", "Q65", "WSPR", "FST4W", "MSK144"]
def stop(self):
self.cancelTimer()

View file

@ -298,7 +298,7 @@ class ServiceHandler(SdrSourceEventClient):
def _getSecondaryDemodulator(self, mod) -> Optional[ServiceDemodulator]:
if isinstance(mod, ServiceDemodulatorChain):
return mod
if mod in ["ft8", "wspr", "jt65", "jt9", "ft4", "fst4", "fst4w", "q65"]:
if mod in ["ft8", "wspr", "jt65", "jt9", "jt4", "ft4", "fst4", "fst4w", "q65"]:
from csdr.chain.digimodes import AudioChopperDemodulator
from owrx.wsjt import WsjtParser
return AudioChopperDemodulator(mod, WsjtParser())

View file

@ -62,6 +62,16 @@ class Fst4wProfileSource(ConfigWiredProfileSource):
return [Fst4wProfile(i) for i in profiles if i in Fst4wProfile.availableIntervals]
class JT4ProfileSource(ConfigWiredProfileSource):
def getPropertiesToWire(self) -> List[str]:
return ["jt4_enabled_submodes"]
def getProfiles(self) -> List[AudioChopperProfile]:
config = Config.get()
profiles = config["jt4_enabled_submodes"] if "jt4_enabled_submodes" in config else []
return [JT4Profile(i) for i in profiles if i in JT4Profile.availableSubmodes]
class Q65ProfileSource(ConfigWiredProfileSource):
def getPropertiesToWire(self) -> List[str]:
return ["q65_enabled_combinations"]
@ -102,6 +112,8 @@ class WsjtProfiles(object):
return Fst4ProfileSource()
elif mode == "fst4w":
return Fst4wProfileSource()
elif mode == "jt4":
return JT4ProfileSource()
elif mode == "q65":
return Q65ProfileSource()
@ -197,6 +209,25 @@ class Fst4wProfile(WsjtProfile):
return "FST4W"
class JT4Profile(WsjtProfile):
availableSubmodes = ["A", "B", "C", "D", "E", "F", "G"]
def __init__(self, submode):
self.submode = submode
def getInterval(self):
return 60
def getSubmode(self):
return self.submode
def decoder_commandline(self, file):
return ["jt9", "-4", "-b", str(self.submode), "-F", "1000" , "-d", str(self.decoding_depth()), file]
def getMode(self):
return "JT4"
class Q65Mode(Enum):
# value is the bandwidth multiplier according to https://physics.princeton.edu/pulsar/k1jt/Q65_Quick_Start.pdf
A = 1