add Q65 mode integration

This commit is contained in:
Jakob Ketterl 2021-02-03 20:11:07 +01:00
parent e66be7c12d
commit d6d6d97a13
10 changed files with 55 additions and 10 deletions

View File

@ -1,7 +1,8 @@
**unreleased** **unreleased**
- Introduced `squelch_auto_margin` config option that allows configuring the auto squelch level - Introduced `squelch_auto_margin` config option that allows configuring the auto squelch level
- Removed `port` configuration option; `rtltcp_compat` takes the port number with the new connectors - Removed `port` configuration option; `rtltcp_compat` takes the port number with the new connectors
- Added support for new WSJT-X modes FST4 and FST4W (only available with WSJT-X 2.3) - Added support for new WSJT-X modes FST4, FST4W (only available with WSJT-X 2.3) and Q65 (only avilable with
WSJT-X 2.4)
- Added support for demodulating M17 digital voice signals using m17-cxx-demod - Added support for demodulating M17 digital voice signals using m17-cxx-demod
- New reporting infrastructure, allowing WSPR and FST4W spots to be sent to wsprnet.org - New reporting infrastructure, allowing WSPR and FST4W spots to be sent to wsprnet.org
- Add some basic filtering capabilities to the map - Add some basic filtering capabilities to the map

View File

@ -318,6 +318,10 @@ fst4_enabled_intervals = [15, 30]
# available values (in seconds): 120, 300, 900, 1800 # available values (in seconds): 120, 300, 900, 1800
fst4w_enabled_intervals = [120, 300] fst4w_enabled_intervals = [120, 300]
# Q65 allows many combinations of intervals and submodes. This setting determines which combinations will be decoded.
# Please use python tuples of (interval: int, mode: str) to specify the combinations. For example:
q65_enabled_combinations = [(30, "A"), (120, "E"), (60, "C")]
# JS8 comes in different speeds: normal, slow, fast, turbo. This setting controls which ones are enabled. # JS8 comes in different speeds: normal, slow, fast, turbo. This setting controls which ones are enabled.
js8_enabled_profiles = ["normal", "slow"] js8_enabled_profiles = ["normal", "slow"]
# JS8 decoding depth; higher value will get more results, but will also consume more cpu # JS8 decoding depth; higher value will get more results, but will also consume more cpu

View File

@ -29,7 +29,7 @@ import math
from functools import partial from functools import partial
from owrx.kiss import KissClient, DirewolfConfig from owrx.kiss import KissClient, DirewolfConfig
from owrx.wsjt import Ft8Profile, WsprProfile, Jt9Profile, Jt65Profile, Ft4Profile, Fst4Profile, Fst4wProfile from owrx.wsjt import Ft8Profile, WsprProfile, Jt9Profile, Jt65Profile, Ft4Profile, Fst4Profile, Fst4wProfile, Q65Profile
from owrx.js8 import Js8Profiles from owrx.js8 import Js8Profiles
from owrx.audio import AudioChopper from owrx.audio import AudioChopper
@ -433,6 +433,8 @@ class dsp(object):
chopper_profiles = Fst4Profile.getEnabledProfiles() chopper_profiles = Fst4Profile.getEnabledProfiles()
elif smd == "fst4w": elif smd == "fst4w":
chopper_profiles = Fst4wProfile.getEnabledProfiles() chopper_profiles = Fst4wProfile.getEnabledProfiles()
elif smd == "q65":
chopper_profiles = Q65Profile.getEnabledProfiles()
if chopper_profiles is not None and len(chopper_profiles): if chopper_profiles is not None and len(chopper_profiles):
chopper = AudioChopper(self, self.secondary_process_demod.stdout, *chopper_profiles) chopper = AudioChopper(self, self.secondary_process_demod.stdout, *chopper_profiles)
chopper.start() chopper.start()
@ -573,7 +575,7 @@ class dsp(object):
def isWsjtMode(self, demodulator=None): def isWsjtMode(self, demodulator=None):
if demodulator is None: if demodulator is None:
demodulator = self.get_secondary_demodulator() demodulator = self.get_secondary_demodulator()
return demodulator in ["ft8", "wspr", "jt65", "jt9", "ft4", "fst4", "fst4w"] return demodulator in ["ft8", "wspr", "jt65", "jt9", "ft4", "fst4", "fst4w", "q65"]
def isJs8(self, demodulator=None): def isJs8(self, demodulator=None):
if demodulator is None: if demodulator is None:

4
debian/changelog vendored
View File

@ -3,8 +3,8 @@ openwebrx (0.21.0) UNRELEASED; urgency=low
auto squelch level auto squelch level
* Removed `port` configuration option; `rtltcp_compat` takes the port number * Removed `port` configuration option; `rtltcp_compat` takes the port number
with the new connectors with the new connectors
* Added support for new WSJT-X modes FST4 and FST4W (only available with * Added support for new WSJT-X modes FST4, FST4W (only available with WSJT-X
WSJT-X 2.3) 2.3) and Q65 (only available with WSJT-X 2.4)
* Added support for demodulating M17 digital voice signals using * Added support for demodulating M17 digital voice signals using
m17-cxx-demod m17-cxx-demod
* New reporting infrastructure, allowing WSPR and FST4W spots to be sent to * New reporting infrastructure, allowing WSPR and FST4W spots to be sent to

View File

@ -1192,6 +1192,7 @@ img.openwebrx-mirror-img
#openwebrx-panel-digimodes[data-mode="js8"] #openwebrx-digimode-content-container, #openwebrx-panel-digimodes[data-mode="js8"] #openwebrx-digimode-content-container,
#openwebrx-panel-digimodes[data-mode="fst4"] #openwebrx-digimode-content-container, #openwebrx-panel-digimodes[data-mode="fst4"] #openwebrx-digimode-content-container,
#openwebrx-panel-digimodes[data-mode="fst4w"] #openwebrx-digimode-content-container, #openwebrx-panel-digimodes[data-mode="fst4w"] #openwebrx-digimode-content-container,
#openwebrx-panel-digimodes[data-mode="q65"] #openwebrx-digimode-content-container,
#openwebrx-panel-digimodes[data-mode="ft8"] #openwebrx-digimode-select-channel, #openwebrx-panel-digimodes[data-mode="ft8"] #openwebrx-digimode-select-channel,
#openwebrx-panel-digimodes[data-mode="wspr"] #openwebrx-digimode-select-channel, #openwebrx-panel-digimodes[data-mode="wspr"] #openwebrx-digimode-select-channel,
#openwebrx-panel-digimodes[data-mode="jt65"] #openwebrx-digimode-select-channel, #openwebrx-panel-digimodes[data-mode="jt65"] #openwebrx-digimode-select-channel,
@ -1201,7 +1202,8 @@ img.openwebrx-mirror-img
#openwebrx-panel-digimodes[data-mode="pocsag"] #openwebrx-digimode-select-channel, #openwebrx-panel-digimodes[data-mode="pocsag"] #openwebrx-digimode-select-channel,
#openwebrx-panel-digimodes[data-mode="js8"] #openwebrx-digimode-select-channel, #openwebrx-panel-digimodes[data-mode="js8"] #openwebrx-digimode-select-channel,
#openwebrx-panel-digimodes[data-mode="fst4"] #openwebrx-digimode-select-channel, #openwebrx-panel-digimodes[data-mode="fst4"] #openwebrx-digimode-select-channel,
#openwebrx-panel-digimodes[data-mode="fst4w"] #openwebrx-digimode-select-channel #openwebrx-panel-digimodes[data-mode="fst4w"] #openwebrx-digimode-select-channel,
#openwebrx-panel-digimodes[data-mode="q65"] #openwebrx-digimode-select-channel
{ {
display: none; display: none;
} }
@ -1215,7 +1217,8 @@ img.openwebrx-mirror-img
#openwebrx-panel-digimodes[data-mode="pocsag"] #openwebrx-digimode-canvas-container, #openwebrx-panel-digimodes[data-mode="pocsag"] #openwebrx-digimode-canvas-container,
#openwebrx-panel-digimodes[data-mode="js8"] #openwebrx-digimode-canvas-container, #openwebrx-panel-digimodes[data-mode="js8"] #openwebrx-digimode-canvas-container,
#openwebrx-panel-digimodes[data-mode="fst4"] #openwebrx-digimode-canvas-container, #openwebrx-panel-digimodes[data-mode="fst4"] #openwebrx-digimode-canvas-container,
#openwebrx-panel-digimodes[data-mode="fst4w"] #openwebrx-digimode-canvas-container #openwebrx-panel-digimodes[data-mode="fst4w"] #openwebrx-digimode-canvas-container,
#openwebrx-panel-digimodes[data-mode="q65"] #openwebrx-digimode-canvas-container
{ {
height: 200px; height: 200px;
margin: -10px; margin: -10px;

View File

@ -158,7 +158,7 @@ DemodulatorPanel.prototype.updatePanels = function() {
var modulation = this.getDemodulator().get_secondary_demod(); var modulation = this.getDemodulator().get_secondary_demod();
$('#openwebrx-panel-digimodes').attr('data-mode', modulation); $('#openwebrx-panel-digimodes').attr('data-mode', modulation);
toggle_panel("openwebrx-panel-digimodes", !!modulation); toggle_panel("openwebrx-panel-digimodes", !!modulation);
toggle_panel("openwebrx-panel-wsjt-message", ['ft8', 'wspr', 'jt65', 'jt9', 'ft4', 'fst4', 'fst4w'].indexOf(modulation) >= 0); toggle_panel("openwebrx-panel-wsjt-message", ['ft8', 'wspr', 'jt65', 'jt9', 'ft4', 'fst4', 'fst4w', "q65"].indexOf(modulation) >= 0);
toggle_panel("openwebrx-panel-js8-message", modulation == "js8"); toggle_panel("openwebrx-panel-js8-message", modulation == "js8");
toggle_panel("openwebrx-panel-packet-message", modulation === "packet"); toggle_panel("openwebrx-panel-packet-message", modulation === "packet");
toggle_panel("openwebrx-panel-pocsag-message", modulation === "pocsag"); toggle_panel("openwebrx-panel-pocsag-message", modulation === "pocsag");

View File

@ -78,7 +78,7 @@ WsjtMessagePanel.prototype.pushMessage = function(msg) {
return $('<div/>').text(input).html() return $('<div/>').text(input).html()
}; };
if (['FT8', 'JT65', 'JT9', 'FT4', 'FST4'].indexOf(msg['mode']) >= 0) { if (['FT8', 'JT65', 'JT9', 'FT4', 'FST4', 'Q65'].indexOf(msg['mode']) >= 0) {
matches = linkedmsg.match(/(.*\s[A-Z0-9]+\s)([A-R]{2}[0-9]{2})$/); matches = linkedmsg.match(/(.*\s[A-Z0-9]+\s)([A-R]{2}[0-9]{2})$/);
if (matches && matches[2] !== 'RR73') { if (matches && matches[2] !== 'RR73') {
linkedmsg = html_escape(matches[1]) + '<a href="map?locator=' + matches[2] + '" target="openwebrx-map">' + matches[2] + '</a>'; linkedmsg = html_escape(matches[1]) + '<a href="map?locator=' + matches[2] + '" target="openwebrx-map">' + matches[2] + '</a>';

View File

@ -98,6 +98,9 @@ class Modes(object):
requirements=["wsjt-x-2-3"], requirements=["wsjt-x-2-3"],
service=True, service=True,
), ),
DigitalMode(
"q65", "Q65", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x-2-4"], service=True
),
DigitalMode( DigitalMode(
"js8", "JS8Call", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["js8call"], service=True "js8", "JS8Call", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["js8call"], service=True
), ),

View File

@ -18,7 +18,7 @@ class PskReporter(Reporter):
interval = 300 interval = 300
def getSupportedModes(self): def getSupportedModes(self):
return ["FT8", "FT4", "JT9", "JT65", "FST4", "JS8"] return ["FT8", "FT4", "JT9", "JT65", "FST4", "JS8", "Q65"]
def stop(self): def stop(self):
self.cancelTimer() self.cancelTimer()

View File

@ -7,6 +7,7 @@ from owrx.parser import Parser
from owrx.audio import AudioChopperProfile from owrx.audio import AudioChopperProfile
from abc import ABC, ABCMeta, abstractmethod from abc import ABC, ABCMeta, abstractmethod
from owrx.config import Config from owrx.config import Config
from enum import Enum
import logging import logging
@ -142,6 +143,37 @@ class Fst4wProfile(WsjtProfile):
return [Fst4wProfile(i) for i in profiles if i in Fst4wProfile.availableIntervals] return [Fst4wProfile(i) for i in profiles if i in Fst4wProfile.availableIntervals]
class Q65Mode(Enum):
A = 1
B = 2
C = 3
D = 4
E = 5
class Q65Profile(WsjtProfile):
availableIntervals = [15, 30, 60, 120, 300]
def __init__(self, interval, mode: Q65Mode):
self.interval = interval
self.mode = mode
def getMode(self):
return "Q65"
def getInterval(self):
return self.interval
def decoder_commandline(self, file):
return ["jt9", "--q65", "-p", str(self.interval), "-b", self.mode.name, "-d", str(self.decoding_depth()), file]
@staticmethod
def getEnabledProfiles():
config = Config.get()
profiles = config["q65_enabled_combinations"] if "q65_enabled_combinations" in config else []
return [Q65Profile(i, Q65Mode[m]) for i, m in profiles if i in Fst4wProfile.availableIntervals]
class WsjtParser(Parser): class WsjtParser(Parser):
def parse(self, messages): def parse(self, messages):
for data in messages: for data in messages: