CW skimmer more usable now.

This commit is contained in:
Marat Fayzullin 2024-11-20 16:22:19 -05:00
parent 5dd807326a
commit e0311ae04c
7 changed files with 117 additions and 58 deletions

View File

@ -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 = [

View File

@ -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)

View File

@ -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;

View File

@ -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

View File

@ -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")

View File

@ -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",

View File

@ -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)