This commit is contained in:
Dawid SQ6EMM 2025-03-20 19:11:15 +01:00 committed by GitHub
commit 358916278b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 60 additions and 9 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 decoding 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

1
debian/changelog vendored
View file

@ -4,6 +4,7 @@ openwebrx (1.3.0) UNRELEASED; urgency=low
* 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 decoding 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

@ -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,8 @@ defaultConfig = PropertyLayer(
wsjt_decoding_depths=PropertyLayer(jt65=1),
fst4_enabled_intervals=[15, 30],
fst4w_enabled_intervals=[120, 300],
jt4_enabled_submodes=["F", "G"],
jt4_frequency_tolerance=20,
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
@ -70,6 +70,11 @@ class DecodingSettingsController(SettingsFormController):
"Default WSJT decoding depth",
infotext="A higher decoding depth will allow more results, but will also consume more cpu",
),
NumberInput(
"jt4_frequency_tolerance",
"Default JT4 frequency tolerance",
infotext="A higher frequency tolerance will allow more decodes, but will also consume more cpu",
),
WsjtDecodingDepthsInput(
"wsjt_decoding_depths",
"Individual decoding depths",
@ -90,6 +95,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"),
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,15 @@ 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 +111,8 @@ class WsjtProfiles(object):
return Fst4ProfileSource()
elif mode == "fst4w":
return Fst4wProfileSource()
elif mode == "jt4":
return JT4ProfileSource()
elif mode == "q65":
return Q65ProfileSource()
@ -197,6 +208,29 @@ 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 frequency_tolerance(self):
config = Config.get()
if "jt4_frequency_tolerance" in config:
return config["jt4_frequency_tolerance"]
# default when no setting is provided
return 20
def decoder_commandline(self, file):
return ["jt9", "-4", "-b", str(self.submode), "-d", str(self.decoding_depth()), "-F", str(self.frequency_tolerance()), 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
@ -271,6 +305,8 @@ class WsjtParser(AudioChopperParser):
return
mode = profile.getMode()
if mode in ["JT4"] and (msg.endswith("$*") or msg.endswith("$#")):
return
if mode in ["WSPR", "FST4W"]:
messageParser = BeaconMessageParser()
else: