CW skimmer more usable now.
This commit is contained in:
parent
5dd807326a
commit
e0311ae04c
|
|
@ -224,7 +224,7 @@ class RdsDemodulator(ServiceDemodulator, DialFrequencyReceiver):
|
||||||
|
|
||||||
|
|
||||||
class CwSkimmerDemodulator(ServiceDemodulator, DialFrequencyReceiver):
|
class CwSkimmerDemodulator(ServiceDemodulator, DialFrequencyReceiver):
|
||||||
def __init__(self, sampleRate: int = 12000, charCount: int = 4, service: bool = False):
|
def __init__(self, sampleRate: int = 48000, charCount: int = 4, service: bool = False):
|
||||||
self.sampleRate = sampleRate
|
self.sampleRate = sampleRate
|
||||||
self.parser = CwSkimmerParser(service)
|
self.parser = CwSkimmerParser(service)
|
||||||
workers = [
|
workers = [
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ class AcarsDecModule(WavFileModule):
|
||||||
|
|
||||||
|
|
||||||
class CwSkimmerModule(ExecModule):
|
class CwSkimmerModule(ExecModule):
|
||||||
def __init__(self, sampleRate: int = 12000, charCount: int = 4):
|
def __init__(self, sampleRate: int = 48000, charCount: int = 4):
|
||||||
cmd = ["csdr-cwskimmer", "-i", "-r", str(sampleRate), "-n", str(charCount)]
|
cmd = ["csdr-cwskimmer", "-i", "-r", str(sampleRate), "-n", str(charCount)]
|
||||||
super().__init__(Format.SHORT, Format.CHAR, cmd)
|
super().__init__(Format.SHORT, Format.CHAR, cmd)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1376,6 +1376,62 @@ img.openwebrx-mirror-img
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#openwebrx-panel-cwskimmer-message {
|
||||||
|
height: 310px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#openwebrx-panel-cwskimmer-message tbody {
|
||||||
|
height: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#openwebrx-panel-cwskimmer-message .freq {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#openwebrx-panel-cwskimmer-message td.freq {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#openwebrx-panel-cwskimmer-message .text {
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#openwebrx-panel-cwskimmer-message td.text {
|
||||||
|
font-family: monospace;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#openwebrx-panel-ism-message {
|
||||||
|
height: 310px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#openwebrx-panel-ism-message tbody {
|
||||||
|
height: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#openwebrx-panel-ism-message .address {
|
||||||
|
width: 120px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#openwebrx-panel-ism-message .device {
|
||||||
|
width: 220px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#openwebrx-panel-ism-message .timestamp {
|
||||||
|
width: 246px;
|
||||||
|
max-width: 486px;
|
||||||
|
white-space: pre;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#openwebrx-panel-ism-message .attr {
|
||||||
|
width: 283px;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
#openwebrx-panel-dsc-message {
|
#openwebrx-panel-dsc-message {
|
||||||
height: 310px;
|
height: 310px;
|
||||||
}
|
}
|
||||||
|
|
@ -1415,37 +1471,6 @@ img.openwebrx-mirror-img
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
#openwebrx-panel-ism-message {
|
|
||||||
height: 310px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#openwebrx-panel-ism-message tbody {
|
|
||||||
height: 280px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#openwebrx-panel-ism-message .address {
|
|
||||||
width: 120px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#openwebrx-panel-ism-message .device {
|
|
||||||
width: 220px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#openwebrx-panel-ism-message .timestamp {
|
|
||||||
width: 246px;
|
|
||||||
max-width: 486px;
|
|
||||||
white-space: pre;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#openwebrx-panel-ism-message .attr {
|
|
||||||
width: 283px;
|
|
||||||
padding-left: 10px;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#openwebrx-panel-sstv-message {
|
#openwebrx-panel-sstv-message {
|
||||||
height: 310px;
|
height: 310px;
|
||||||
width: 365px;
|
width: 365px;
|
||||||
|
|
|
||||||
|
|
@ -881,7 +881,6 @@ $.fn.faxMessagePanel = function() {
|
||||||
CwSkimmerMessagePanel = function(el) {
|
CwSkimmerMessagePanel = function(el) {
|
||||||
MessagePanel.call(this, el);
|
MessagePanel.call(this, el);
|
||||||
this.initClearTimer();
|
this.initClearTimer();
|
||||||
this.freqs = [];
|
|
||||||
this.texts = [];
|
this.texts = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -895,8 +894,8 @@ CwSkimmerMessagePanel.prototype.render = function() {
|
||||||
$(this.el).append($(
|
$(this.el).append($(
|
||||||
'<table width="100%">' +
|
'<table width="100%">' +
|
||||||
'<thead><tr>' +
|
'<thead><tr>' +
|
||||||
'<th class="frequency">Freq</th>' +
|
'<th class="freq">Freq</th>' +
|
||||||
'<th class="data">Message</th>' +
|
'<th class="text">Text</th>' +
|
||||||
'</tr></thead>' +
|
'</tr></thead>' +
|
||||||
'<tbody></tbody>' +
|
'<tbody></tbody>' +
|
||||||
'</table>'
|
'</table>'
|
||||||
|
|
@ -907,30 +906,40 @@ CwSkimmerMessagePanel.prototype.pushMessage = function(msg) {
|
||||||
// Must have some text
|
// Must have some text
|
||||||
if (!msg.text) return;
|
if (!msg.text) return;
|
||||||
|
|
||||||
// Find existing frequency
|
// Clear cache if requested
|
||||||
var j = this.freqs.indexOf(msg.freq);
|
if (msg.changed) this.texts = [];
|
||||||
if (j >= 0) {
|
|
||||||
|
// Current time
|
||||||
|
var now = Date.now();
|
||||||
|
|
||||||
|
// Modify or add a new entry
|
||||||
|
var j = this.texts.findIndex(function(x) { return x.freq >= msg.freq });
|
||||||
|
if (j < 0) {
|
||||||
|
// Append a new entry
|
||||||
|
this.texts.push({ freq: msg.freq, text: msg.text, ts: now });
|
||||||
|
} else if (this.texts[j].freq == msg.freq) {
|
||||||
// Update existing entry
|
// Update existing entry
|
||||||
this.texts[j] = (this.texts[j] + msg.text).slice(-64);
|
this.texts[j].text = (this.texts[j].text + msg.text).slice(-64);
|
||||||
|
this.texts[j].ts = now;
|
||||||
} else {
|
} else {
|
||||||
// Add a new entry
|
// Insert a new entry
|
||||||
this.freqs.push(msg.freq);
|
this.texts.splice(j, 0, { freq: msg.freq, text: msg.text, ts: now });
|
||||||
this.texts.push(msg.text);
|
|
||||||
// Limit the number of active frequencies
|
|
||||||
// if (this.freqs.length > 16) {
|
|
||||||
// this.freqs.shift();
|
|
||||||
// this.texts.shift();
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate table body
|
// Generate table body
|
||||||
var body = '';
|
var body = '';
|
||||||
for (j = 0 ; j < this.freqs.length ; j++) {
|
for (var j = 0 ; j < this.texts.length ; j++) {
|
||||||
body +=
|
// Limit the lifetime of entries depending on their length
|
||||||
'<tr style="color:black;background-color:' + (j&1? '#E0FFE0':'#FFFFFF') +
|
var cutoff = 5000 * this.texts[j].text.length;
|
||||||
';"><td style="text-align:right;">' + Math.round(this.freqs[j]/10)/100 +
|
if (now - this.texts[j].ts >= cutoff) {
|
||||||
'</td><td style="font-family:monospace;white-space:nowrap;">' +
|
this.texts.splice(j--, 1);
|
||||||
this.texts[j] + '</td></tr>\n';
|
} else {
|
||||||
|
var f = Math.floor(this.texts[j].freq / 100.0) / 10.0;
|
||||||
|
body +=
|
||||||
|
'<tr style="color:black;background-color:' + (j&1? '#E0FFE0':'#FFFFFF') +
|
||||||
|
';"><td class="freq">' + f.toFixed(1) +
|
||||||
|
'</td><td class="text">' + this.texts[j].text + '</td></tr>\n';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign new table body
|
// Assign new table body
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,7 @@ class FeatureDetector(object):
|
||||||
"mqtt": ["paho_mqtt"],
|
"mqtt": ["paho_mqtt"],
|
||||||
"hdradio": ["nrsc5"],
|
"hdradio": ["nrsc5"],
|
||||||
"rigcontrol": ["hamlib"],
|
"rigcontrol": ["hamlib"],
|
||||||
|
"cwskimmer": ["csdr_cwskimmer"],
|
||||||
}
|
}
|
||||||
|
|
||||||
def feature_availability(self):
|
def feature_availability(self):
|
||||||
|
|
@ -775,3 +776,11 @@ class FeatureDetector(object):
|
||||||
The `hamlib` package is available in most Linux distributions.
|
The `hamlib` package is available in most Linux distributions.
|
||||||
"""
|
"""
|
||||||
return self.command_is_runnable("rigctl -V")
|
return self.command_is_runnable("rigctl -V")
|
||||||
|
|
||||||
|
def has_csdr_cwskimmer(self):
|
||||||
|
"""
|
||||||
|
OpenWebRX uses the [CSDR CWSkimmer](https://github.com/luarvique/csdr-cwskimmer)
|
||||||
|
to decode multiple CW signals at once. You can install the
|
||||||
|
`csdr-cwskimmer` package from the OpenWebRX+ repositories.
|
||||||
|
"""
|
||||||
|
return self.command_is_runnable("csdr-cwskimmer -h")
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,9 @@ class Modes(object):
|
||||||
service=True,
|
service=True,
|
||||||
squelch=False,
|
squelch=False,
|
||||||
),
|
),
|
||||||
|
# Replaced by Jakob's RTTY decoder
|
||||||
|
# DigitalMode("mfrtty170", "RTTY-170", underlying=["usb"]),
|
||||||
|
# DigitalMode("mfrtty450", "RTTY-450", underlying=["usb"]),
|
||||||
# Replaced by the general paging decoder (both POCSAG and FLEX)
|
# Replaced by the general paging decoder (both POCSAG and FLEX)
|
||||||
# DigitalMode(
|
# DigitalMode(
|
||||||
# "pocsag",
|
# "pocsag",
|
||||||
|
|
@ -194,10 +197,14 @@ class Modes(object):
|
||||||
squelch=False,
|
squelch=False,
|
||||||
),
|
),
|
||||||
DigitalMode("cwdecoder", "CW Decoder", underlying=["usb", "lsb"]),
|
DigitalMode("cwdecoder", "CW Decoder", underlying=["usb", "lsb"]),
|
||||||
DigitalMode("cwskimmer", "CW Skimmer", underlying=["usb", "lsb"]),
|
DigitalMode(
|
||||||
# Replaced by Jakob's RTTY decoder
|
"cwskimmer",
|
||||||
# DigitalMode("mfrtty170", "RTTY-170", underlying=["usb"]),
|
"CW Skimmer",
|
||||||
# DigitalMode("mfrtty450", "RTTY-450", underlying=["usb"]),
|
underlying=["usbd"],
|
||||||
|
requirements=["cwskimmer"],
|
||||||
|
service=False,
|
||||||
|
squelch=False,
|
||||||
|
),
|
||||||
DigitalMode(
|
DigitalMode(
|
||||||
"sstv",
|
"sstv",
|
||||||
"SSTV",
|
"SSTV",
|
||||||
|
|
|
||||||
|
|
@ -419,6 +419,7 @@ class EasParser(TextParser):
|
||||||
class CwSkimmerParser(TextParser):
|
class CwSkimmerParser(TextParser):
|
||||||
def __init__(self, service: bool = False):
|
def __init__(self, service: bool = False):
|
||||||
self.reLine = re.compile("^(\d+):(.+)$")
|
self.reLine = re.compile("^(\d+):(.+)$")
|
||||||
|
self.freqChanged = False
|
||||||
# Construct parent object
|
# Construct parent object
|
||||||
super().__init__(filePrefix="CW", service=service)
|
super().__init__(filePrefix="CW", service=service)
|
||||||
|
|
||||||
|
|
@ -438,7 +439,15 @@ class CwSkimmerParser(TextParser):
|
||||||
# Add frequency, if known
|
# Add frequency, if known
|
||||||
if self.frequency:
|
if self.frequency:
|
||||||
out["freq"] = self.frequency + freq
|
out["freq"] = self.frequency + freq
|
||||||
|
# Report frequency changes
|
||||||
|
if self.freqChanged:
|
||||||
|
self.freqChanged = False
|
||||||
|
out["changed"] = True
|
||||||
# Done
|
# Done
|
||||||
return out
|
return out
|
||||||
# No result
|
# No result
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def setDialFrequency(self, frequency: int) -> None:
|
||||||
|
self.freqChanged = frequency != self.frequency
|
||||||
|
super().setDialFrequency(frequency)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue