Merge branch 'develop' into m17
This commit is contained in:
commit
06f3499b6d
@ -269,6 +269,10 @@ waterfall_auto_level_margin = {"min": 3, "max": 10, "min_range": 50}
|
|||||||
# \_waterfall_auto_level_margin["min"]_/ |__ current_min_power_level | \_waterfall_auto_level_margin["max"]_/
|
# \_waterfall_auto_level_margin["min"]_/ |__ current_min_power_level | \_waterfall_auto_level_margin["max"]_/
|
||||||
# current_max_power_level __|
|
# current_max_power_level __|
|
||||||
|
|
||||||
|
# This setting allows you to modify the precision of the frequency displays in OpenWebRX.
|
||||||
|
# Set this to the number of digits you would like to see:
|
||||||
|
frequency_display_precision = 4
|
||||||
|
|
||||||
# This setting tells the auto-squelch the offset to add to the current signal level to use as the new squelch level.
|
# This setting tells the auto-squelch the offset to add to the current signal level to use as the new squelch level.
|
||||||
# Lowering this setting will give you a more sensitive squelch, but it may also cause unwanted squelch openings when
|
# Lowering this setting will give you a more sensitive squelch, but it may also cause unwanted squelch openings when
|
||||||
# using the auto squelch.
|
# using the auto squelch.
|
||||||
@ -341,6 +345,8 @@ aprs_symbols_path = "/usr/share/aprs-symbols/png"
|
|||||||
# this also uses the receiver_gps setting from above, so make sure it contains a correct locator
|
# this also uses the receiver_gps setting from above, so make sure it contains a correct locator
|
||||||
pskreporter_enabled = False
|
pskreporter_enabled = False
|
||||||
pskreporter_callsign = "N0CALL"
|
pskreporter_callsign = "N0CALL"
|
||||||
|
# optional antenna information, uncomment to enable
|
||||||
|
#pskreporter_antenna_information = "Dipole"
|
||||||
|
|
||||||
# === Web admin settings ===
|
# === Web admin settings ===
|
||||||
# this feature is experimental at the moment. it should not be enabled on shared receivers since it allows remote
|
# this feature is experimental at the moment. it should not be enabled on shared receivers since it allows remote
|
||||||
|
2
debian/postinst
vendored
2
debian/postinst
vendored
@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -euxo pipefail
|
set -euxo pipefail
|
||||||
|
|
||||||
adduser --system --group --no-create-home --home /nonexistant openwebrx
|
adduser --system --group --no-create-home --home /nonexistent --quiet openwebrx
|
||||||
usermod -aG plugdev openwebrx
|
usermod -aG plugdev openwebrx
|
||||||
|
|
||||||
#DEBHELPER#
|
#DEBHELPER#
|
@ -981,6 +981,7 @@ img.openwebrx-mirror-img
|
|||||||
|
|
||||||
.openwebrx-message-panel {
|
.openwebrx-message-panel {
|
||||||
height: 180px;
|
height: 180px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.openwebrx-message-panel tbody {
|
.openwebrx-message-panel tbody {
|
||||||
@ -1146,6 +1147,8 @@ img.openwebrx-mirror-img
|
|||||||
#openwebrx-panel-digimodes[data-mode="packet"] #openwebrx-digimode-content-container,
|
#openwebrx-panel-digimodes[data-mode="packet"] #openwebrx-digimode-content-container,
|
||||||
#openwebrx-panel-digimodes[data-mode="pocsag"] #openwebrx-digimode-content-container,
|
#openwebrx-panel-digimodes[data-mode="pocsag"] #openwebrx-digimode-content-container,
|
||||||
#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="fst4w"] #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,
|
||||||
@ -1153,7 +1156,9 @@ img.openwebrx-mirror-img
|
|||||||
#openwebrx-panel-digimodes[data-mode="ft4"] #openwebrx-digimode-select-channel,
|
#openwebrx-panel-digimodes[data-mode="ft4"] #openwebrx-digimode-select-channel,
|
||||||
#openwebrx-panel-digimodes[data-mode="packet"] #openwebrx-digimode-select-channel,
|
#openwebrx-panel-digimodes[data-mode="packet"] #openwebrx-digimode-select-channel,
|
||||||
#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="fst4w"] #openwebrx-digimode-select-channel
|
||||||
{
|
{
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -1165,7 +1170,9 @@ img.openwebrx-mirror-img
|
|||||||
#openwebrx-panel-digimodes[data-mode="ft4"] #openwebrx-digimode-canvas-container,
|
#openwebrx-panel-digimodes[data-mode="ft4"] #openwebrx-digimode-canvas-container,
|
||||||
#openwebrx-panel-digimodes[data-mode="packet"] #openwebrx-digimode-canvas-container,
|
#openwebrx-panel-digimodes[data-mode="packet"] #openwebrx-digimode-canvas-container,
|
||||||
#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="fst4w"] #openwebrx-digimode-canvas-container
|
||||||
{
|
{
|
||||||
height: 200px;
|
height: 200px;
|
||||||
margin: -10px;
|
margin: -10px;
|
||||||
|
@ -60,40 +60,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="openwebrx-panel openwebrx-message-panel" id="openwebrx-panel-wsjt-message" style="display: none; width: 619px;" data-panel-name="wsjt-message">
|
<div class="openwebrx-panel openwebrx-message-panel" id="openwebrx-panel-wsjt-message" style="display: none; width: 619px;" data-panel-name="wsjt-message"></div>
|
||||||
<thead><tr>
|
<div class="openwebrx-panel openwebrx-message-panel" id="openwebrx-panel-js8-message" style="display:none; width: 619px;" data-panel-name="js8-message"></div>
|
||||||
<th>UTC</th>
|
<div class="openwebrx-panel openwebrx-message-panel" id="openwebrx-panel-packet-message" style="display: none; width: 619px;" data-panel-name="aprs-message"></div>
|
||||||
<th class="decimal">dB</th>
|
<div class="openwebrx-panel openwebrx-message-panel" id="openwebrx-panel-pocsag-message" style="display: none; width: 619px;" data-panel-name="pocsag-message"></div>
|
||||||
<th class="decimal">DT</th>
|
|
||||||
<th class="decimal freq">Freq</th>
|
|
||||||
<th class="message">Message</th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody></tbody>
|
|
||||||
</table>
|
|
||||||
<table class="openwebrx-panel openwebrx-message-panel" id="openwebrx-panel-js8-message" style="display:none; width: 619px;" data-panel-name="js8-message">
|
|
||||||
<thead><tr>
|
|
||||||
<th>UTC</th>
|
|
||||||
<th class="decimal freq">Freq</th>
|
|
||||||
<th class="message">Message</th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody></tbody>
|
|
||||||
</table>
|
|
||||||
<table class="openwebrx-panel openwebrx-message-panel" id="openwebrx-panel-packet-message" style="display: none; width: 619px;" data-panel-name="aprs-message">
|
|
||||||
<thead><tr>
|
|
||||||
<th>UTC</th>
|
|
||||||
<th class="callsign">Callsign</th>
|
|
||||||
<th class="coord">Coord</th>
|
|
||||||
<th class="message">Comment</th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody></tbody>
|
|
||||||
</table>
|
|
||||||
<table class="openwebrx-panel openwebrx-message-panel" id="openwebrx-panel-pocsag-message" style="display: none; width: 619px;" data-panel-name="pocsag-message">
|
|
||||||
<thead><tr>
|
|
||||||
<th class="address">Address</th>
|
|
||||||
<th class="message">Message</th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody></tbody>
|
|
||||||
</table>
|
|
||||||
<div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-ysf" style="display: none;" data-panel-name="metadata-ysf">
|
<div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-ysf" style="display: none;" data-panel-name="metadata-ysf">
|
||||||
<div class="openwebrx-meta-frame">
|
<div class="openwebrx-meta-frame">
|
||||||
<div class="openwebrx-meta-slot">
|
<div class="openwebrx-meta-slot">
|
||||||
|
@ -11,6 +11,8 @@ function DemodulatorPanel(el) {
|
|||||||
self.getDemodulator().set_offset_frequency(freq - self.center_freq);
|
self.getDemodulator().set_offset_frequency(freq - self.center_freq);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.mouseFrequencyDisplay = el.find('.webrx-mouse-freq').frequencyDisplay();
|
||||||
|
|
||||||
Modes.registerModePanel(this);
|
Modes.registerModePanel(this);
|
||||||
el.on('click', '.openwebrx-demodulator-button', function() {
|
el.on('click', '.openwebrx-demodulator-button', function() {
|
||||||
var modulation = $(this).data('modulation');
|
var modulation = $(this).data('modulation');
|
||||||
@ -155,7 +157,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'].indexOf(modulation) >= 0);
|
toggle_panel("openwebrx-panel-wsjt-message", ['ft8', 'wspr', 'jt65', 'jt9', 'ft4', 'fst4', 'fst4w'].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");
|
||||||
@ -332,6 +334,15 @@ DemodulatorPanel.prototype.getSquelchMargin = function() {
|
|||||||
return this.squelchMargin;
|
return this.squelchMargin;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DemodulatorPanel.prototype.setMouseFrequency = function(freq) {
|
||||||
|
this.mouseFrequencyDisplay.setFrequency(freq);
|
||||||
|
};
|
||||||
|
|
||||||
|
DemodulatorPanel.prototype.setFrequencyPrecision = function(precision) {
|
||||||
|
this.tuneableFrequencyDisplay.setFrequencyPrecision(precision);
|
||||||
|
this.mouseFrequencyDisplay.setFrequencyPrecision(precision);
|
||||||
|
};
|
||||||
|
|
||||||
$.fn.demodulatorPanel = function(){
|
$.fn.demodulatorPanel = function(){
|
||||||
if (!this.data('panel')) {
|
if (!this.data('panel')) {
|
||||||
this.data('panel', new DemodulatorPanel(this));
|
this.data('panel', new DemodulatorPanel(this));
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
function FrequencyDisplay(element) {
|
function FrequencyDisplay(element) {
|
||||||
this.element = $(element);
|
this.element = $(element);
|
||||||
this.digits = [];
|
this.digits = [];
|
||||||
|
this.precision = 4;
|
||||||
this.setupElements();
|
this.setupElements();
|
||||||
this.setFrequency(0);
|
this.setFrequency(0);
|
||||||
}
|
}
|
||||||
@ -14,7 +15,10 @@ FrequencyDisplay.prototype.setupElements = function() {
|
|||||||
|
|
||||||
FrequencyDisplay.prototype.setFrequency = function(freq) {
|
FrequencyDisplay.prototype.setFrequency = function(freq) {
|
||||||
this.frequency = freq;
|
this.frequency = freq;
|
||||||
var formatted = (freq / 1e6).toLocaleString(undefined, {maximumFractionDigits: 4, minimumFractionDigits: 4});
|
var formatted = (freq / 1e6).toLocaleString(
|
||||||
|
undefined,
|
||||||
|
{maximumFractionDigits: this.precision, minimumFractionDigits: this.precision}
|
||||||
|
);
|
||||||
var children = this.digitContainer.children();
|
var children = this.digitContainer.children();
|
||||||
for (var i = 0; i < formatted.length; i++) {
|
for (var i = 0; i < formatted.length; i++) {
|
||||||
if (!this.digits[i]) {
|
if (!this.digits[i]) {
|
||||||
@ -34,6 +38,12 @@ FrequencyDisplay.prototype.setFrequency = function(freq) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FrequencyDisplay.prototype.setFrequencyPrecision = function(precision) {
|
||||||
|
if (!precision) return;
|
||||||
|
this.precision = precision;
|
||||||
|
this.setFrequency(this.frequency);
|
||||||
|
};
|
||||||
|
|
||||||
function TuneableFrequencyDisplay(element) {
|
function TuneableFrequencyDisplay(element) {
|
||||||
FrequencyDisplay.call(this, element);
|
FrequencyDisplay.call(this, element);
|
||||||
this.setupEvents();
|
this.setupEvents();
|
||||||
|
@ -11,8 +11,7 @@ function Header(el) {
|
|||||||
|
|
||||||
Header.prototype.setDetails = function(details) {
|
Header.prototype.setDetails = function(details) {
|
||||||
this.el.find('#webrx-rx-title').html(details['receiver_name']);
|
this.el.find('#webrx-rx-title').html(details['receiver_name']);
|
||||||
var query = encodeURIComponent(details['receiver_gps']['lat'] + ',' + details['receiver_gps']['lon']);
|
this.el.find('#webrx-rx-desc').html(details['receiver_location'] + ' | Loc: ' + details['locator'] + ', ASL: ' + details['receiver_asl'] + ' m');
|
||||||
this.el.find('#webrx-rx-desc').html(details['receiver_location'] + ' | Loc: ' + details['locator'] + ', ASL: ' + details['receiver_asl'] + ' m, <a href="https://www.google.com/maps/search/?api=1&query=' + query + '" target="_blank">[maps]</a>');
|
|
||||||
this.el.find('#webrx-rx-photo-title').html(details['photo_title']);
|
this.el.find('#webrx-rx-photo-title').html(details['photo_title']);
|
||||||
this.el.find('#webrx-rx-photo-desc').html(details['photo_desc']);
|
this.el.find('#webrx-rx-photo-desc').html(details['photo_desc']);
|
||||||
};
|
};
|
||||||
|
@ -100,7 +100,13 @@ Js8Thread.prototype.purgeOldMessages = function() {
|
|||||||
return this.messages.length;
|
return this.messages.length;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Js8Thread.prototype.purge = function() {
|
||||||
|
this.message = [];
|
||||||
|
this.el.remove();
|
||||||
|
};
|
||||||
|
|
||||||
Js8Threader = function(el){
|
Js8Threader = function(el){
|
||||||
|
MessagePanel.call(this, el);
|
||||||
this.threads = [];
|
this.threads = [];
|
||||||
this.tbody = $(el).find('tbody');
|
this.tbody = $(el).find('tbody');
|
||||||
var me = this;
|
var me = this;
|
||||||
@ -109,6 +115,28 @@ Js8Threader = function(el){
|
|||||||
}, 15000);
|
}, 15000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Js8Threader.prototype = new MessagePanel();
|
||||||
|
|
||||||
|
Js8Threader.prototype.render = function() {
|
||||||
|
$(this.el).append($(
|
||||||
|
'<table>' +
|
||||||
|
'<thead><tr>' +
|
||||||
|
'<th>UTC</th>' +
|
||||||
|
'<th class="decimal freq">Freq</th>' +
|
||||||
|
'<th class="message">Message</th>' +
|
||||||
|
'</tr></thead>' +
|
||||||
|
'<tbody></tbody>' +
|
||||||
|
'</table>'
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
Js8Threader.prototype.clearMessages = function() {
|
||||||
|
this.threads.forEach(function(t) {
|
||||||
|
t.purge();
|
||||||
|
});
|
||||||
|
this.threads = [];
|
||||||
|
};
|
||||||
|
|
||||||
Js8Threader.prototype.purgeOldMessages = function() {
|
Js8Threader.prototype.purgeOldMessages = function() {
|
||||||
this.threads = this.threads.filter(function(t) {
|
this.threads = this.threads.filter(function(t) {
|
||||||
return t.purgeOldMessages();
|
return t.purgeOldMessages();
|
||||||
|
247
htdocs/lib/MessagePanel.js
Normal file
247
htdocs/lib/MessagePanel.js
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
function MessagePanel(el) {
|
||||||
|
this.el = el;
|
||||||
|
this.render();
|
||||||
|
this.initClearButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
MessagePanel.prototype.render = function() {
|
||||||
|
};
|
||||||
|
|
||||||
|
MessagePanel.prototype.pushMessage = function(message) {
|
||||||
|
};
|
||||||
|
|
||||||
|
// automatic clearing is not enabled by default. call this method from the constructor to enable
|
||||||
|
MessagePanel.prototype.initClearTimer = function() {
|
||||||
|
var me = this;
|
||||||
|
if (me.removalInterval) clearInterval(me.removalInterval);
|
||||||
|
me.removalInterval = setInterval(function () {
|
||||||
|
me.clearMessages(1000);
|
||||||
|
}, 15000);
|
||||||
|
};
|
||||||
|
|
||||||
|
MessagePanel.prototype.clearMessages = function(toRemain) {
|
||||||
|
var $elements = $(this.el).find('tbody tr');
|
||||||
|
// limit to 1000 entries in the list since browsers get laggy at some point
|
||||||
|
var toRemove = $elements.length - toRemain;
|
||||||
|
if (toRemove <= 0) return;
|
||||||
|
$elements.slice(0, toRemove).remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
MessagePanel.prototype.initClearButton = function() {
|
||||||
|
var me = this;
|
||||||
|
me.clearButton = $(
|
||||||
|
'<div class="openwebrx-button">Clear</div>'
|
||||||
|
);
|
||||||
|
me.clearButton.css({
|
||||||
|
position: 'absolute',
|
||||||
|
top: '10px',
|
||||||
|
right: '10px'
|
||||||
|
});
|
||||||
|
me.clearButton.on('click', function() {
|
||||||
|
me.clearMessages(0);
|
||||||
|
});
|
||||||
|
$(me.el).append(me.clearButton);
|
||||||
|
};
|
||||||
|
|
||||||
|
function WsjtMessagePanel(el) {
|
||||||
|
MessagePanel.call(this, el);
|
||||||
|
this.initClearTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
WsjtMessagePanel.prototype = new MessagePanel();
|
||||||
|
|
||||||
|
WsjtMessagePanel.prototype.render = function() {
|
||||||
|
$(this.el).append($(
|
||||||
|
'<table>' +
|
||||||
|
'<thead><tr>' +
|
||||||
|
'<th>UTC</th>' +
|
||||||
|
'<th class="decimal">dB</th>' +
|
||||||
|
'<th class="decimal">DT</th>' +
|
||||||
|
'<th class="decimal freq">Freq</th>' +
|
||||||
|
'<th class="message">Message</th>' +
|
||||||
|
'</tr></thead>' +
|
||||||
|
'<tbody></tbody>' +
|
||||||
|
'</table>'
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
WsjtMessagePanel.prototype.pushMessage = function(msg) {
|
||||||
|
var $b = $(this.el).find('tbody');
|
||||||
|
var t = new Date(msg['timestamp']);
|
||||||
|
var pad = function (i) {
|
||||||
|
return ('' + i).padStart(2, "0");
|
||||||
|
};
|
||||||
|
var linkedmsg = msg['msg'];
|
||||||
|
var matches;
|
||||||
|
|
||||||
|
var html_escape = function(input) {
|
||||||
|
return $('<div/>').text(input).html()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (['FT8', 'JT65', 'JT9', 'FT4', 'FST4', 'FST4W'].indexOf(msg['mode']) >= 0) {
|
||||||
|
matches = linkedmsg.match(/(.*\s[A-Z0-9]+\s)([A-R]{2}[0-9]{2})$/);
|
||||||
|
if (matches && matches[2] !== 'RR73') {
|
||||||
|
linkedmsg = html_escape(matches[1]) + '<a href="map?locator=' + matches[2] + '" target="openwebrx-map">' + matches[2] + '</a>';
|
||||||
|
} else {
|
||||||
|
linkedmsg = html_escape(linkedmsg);
|
||||||
|
}
|
||||||
|
} else if (msg['mode'] === 'WSPR') {
|
||||||
|
matches = linkedmsg.match(/([A-Z0-9]*\s)([A-R]{2}[0-9]{2})(\s[0-9]+)/);
|
||||||
|
if (matches) {
|
||||||
|
linkedmsg = html_escape(matches[1]) + '<a href="map?locator=' + matches[2] + '" target="openwebrx-map">' + matches[2] + '</a>' + html_escape(matches[3]);
|
||||||
|
} else {
|
||||||
|
linkedmsg = html_escape(linkedmsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$b.append($(
|
||||||
|
'<tr data-timestamp="' + msg['timestamp'] + '">' +
|
||||||
|
'<td>' + pad(t.getUTCHours()) + pad(t.getUTCMinutes()) + pad(t.getUTCSeconds()) + '</td>' +
|
||||||
|
'<td class="decimal">' + msg['db'] + '</td>' +
|
||||||
|
'<td class="decimal">' + msg['dt'] + '</td>' +
|
||||||
|
'<td class="decimal freq">' + msg['freq'] + '</td>' +
|
||||||
|
'<td class="message">' + linkedmsg + '</td>' +
|
||||||
|
'</tr>'
|
||||||
|
));
|
||||||
|
$b.scrollTop($b[0].scrollHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
$.fn.wsjtMessagePanel = function(){
|
||||||
|
if (!this.data('panel')) {
|
||||||
|
this.data('panel', new WsjtMessagePanel(this));
|
||||||
|
};
|
||||||
|
return this.data('panel');
|
||||||
|
};
|
||||||
|
|
||||||
|
function PacketMessagePanel(el) {
|
||||||
|
MessagePanel.call(this, el);
|
||||||
|
this.initClearTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
PacketMessagePanel.prototype = new MessagePanel();
|
||||||
|
|
||||||
|
PacketMessagePanel.prototype.render = function() {
|
||||||
|
$(this.el).append($(
|
||||||
|
'<table>' +
|
||||||
|
'<thead><tr>' +
|
||||||
|
'<th>UTC</th>' +
|
||||||
|
'<th class="callsign">Callsign</th>' +
|
||||||
|
'<th class="coord">Coord</th>' +
|
||||||
|
'<th class="message">Comment</th>' +
|
||||||
|
'</tr></thead>' +
|
||||||
|
'<tbody></tbody>' +
|
||||||
|
'</table>'
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
PacketMessagePanel.prototype.pushMessage = function(msg) {
|
||||||
|
var $b = $(this.el).find('tbody');
|
||||||
|
var pad = function (i) {
|
||||||
|
return ('' + i).padStart(2, "0");
|
||||||
|
};
|
||||||
|
|
||||||
|
if (msg.type && msg.type === 'thirdparty' && msg.data) {
|
||||||
|
msg = msg.data;
|
||||||
|
}
|
||||||
|
var source = msg.source;
|
||||||
|
if (msg.type) {
|
||||||
|
if (msg.type === 'item') {
|
||||||
|
source = msg.item;
|
||||||
|
}
|
||||||
|
if (msg.type === 'object') {
|
||||||
|
source = msg.object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var timestamp = '';
|
||||||
|
if (msg.timestamp) {
|
||||||
|
var t = new Date(msg.timestamp);
|
||||||
|
timestamp = pad(t.getUTCHours()) + pad(t.getUTCMinutes()) + pad(t.getUTCSeconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
var link = '';
|
||||||
|
var classes = [];
|
||||||
|
var styles = {};
|
||||||
|
var overlay = '';
|
||||||
|
var stylesToString = function (s) {
|
||||||
|
return $.map(s, function (value, key) {
|
||||||
|
return key + ':' + value + ';'
|
||||||
|
}).join('')
|
||||||
|
};
|
||||||
|
if (msg.symbol) {
|
||||||
|
classes.push('aprs-symbol');
|
||||||
|
classes.push('aprs-symboltable-' + (msg.symbol.table === '/' ? 'normal' : 'alternate'));
|
||||||
|
styles['background-position-x'] = -(msg.symbol.index % 16) * 15 + 'px';
|
||||||
|
styles['background-position-y'] = -Math.floor(msg.symbol.index / 16) * 15 + 'px';
|
||||||
|
if (msg.symbol.table !== '/' && msg.symbol.table !== '\\') {
|
||||||
|
var s = {};
|
||||||
|
s['background-position-x'] = -(msg.symbol.tableindex % 16) * 15 + 'px';
|
||||||
|
s['background-position-y'] = -Math.floor(msg.symbol.tableindex / 16) * 15 + 'px';
|
||||||
|
overlay = '<div class="aprs-symbol aprs-symboltable-overlay" style="' + stylesToString(s) + '"></div>';
|
||||||
|
}
|
||||||
|
} else if (msg.lat && msg.lon) {
|
||||||
|
classes.push('openwebrx-maps-pin');
|
||||||
|
}
|
||||||
|
var attrs = [
|
||||||
|
'class="' + classes.join(' ') + '"',
|
||||||
|
'style="' + stylesToString(styles) + '"'
|
||||||
|
].join(' ');
|
||||||
|
if (msg.lat && msg.lon) {
|
||||||
|
link = '<a ' + attrs + ' href="map?callsign=' + source + '" target="openwebrx-map">' + overlay + '</a>';
|
||||||
|
} else {
|
||||||
|
link = '<div ' + attrs + '>' + overlay + '</div>'
|
||||||
|
}
|
||||||
|
|
||||||
|
$b.append($(
|
||||||
|
'<tr>' +
|
||||||
|
'<td>' + timestamp + '</td>' +
|
||||||
|
'<td class="callsign">' + source + '</td>' +
|
||||||
|
'<td class="coord">' + link + '</td>' +
|
||||||
|
'<td class="message">' + (msg.comment || msg.message || '') + '</td>' +
|
||||||
|
'</tr>'
|
||||||
|
));
|
||||||
|
$b.scrollTop($b[0].scrollHeight);
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.packetMessagePanel = function() {
|
||||||
|
if (!this.data('panel')) {
|
||||||
|
this.data('panel', new PacketMessagePanel(this));
|
||||||
|
};
|
||||||
|
return this.data('panel');
|
||||||
|
};
|
||||||
|
|
||||||
|
PocsagMessagePanel = function(el) {
|
||||||
|
MessagePanel.call(this, el);
|
||||||
|
this.initClearTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
PocsagMessagePanel.prototype = new MessagePanel();
|
||||||
|
|
||||||
|
PocsagMessagePanel.prototype.render = function() {
|
||||||
|
$(this.el).append($(
|
||||||
|
'<table>' +
|
||||||
|
'<thead><tr>' +
|
||||||
|
'<th class="address">Address</th>' +
|
||||||
|
'<th class="message">Message</th>' +
|
||||||
|
'</tr></thead>' +
|
||||||
|
'<tbody></tbody>' +
|
||||||
|
'</table>'
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
PocsagMessagePanel.prototype.pushMessage = function(msg) {
|
||||||
|
var $b = $(this.el).find('tbody');
|
||||||
|
$b.append($(
|
||||||
|
'<tr>' +
|
||||||
|
'<td class="address">' + msg.address + '</td>' +
|
||||||
|
'<td class="message">' + msg.message + '</td>' +
|
||||||
|
'</tr>'
|
||||||
|
));
|
||||||
|
$b.scrollTop($b[0].scrollHeight);
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.pocsagMessagePanel = function() {
|
||||||
|
if (!this.data('panel')) {
|
||||||
|
this.data('panel', new PocsagMessagePanel(this));
|
||||||
|
};
|
||||||
|
return this.data('panel');
|
||||||
|
};
|
@ -30,6 +30,7 @@
|
|||||||
var map;
|
var map;
|
||||||
var markers = {};
|
var markers = {};
|
||||||
var rectangles = {};
|
var rectangles = {};
|
||||||
|
var receiverMarker;
|
||||||
var updateQueue = [];
|
var updateQueue = [];
|
||||||
|
|
||||||
// reasonable default; will be overriden by server
|
// reasonable default; will be overriden by server
|
||||||
@ -44,7 +45,12 @@
|
|||||||
if (!colorKeys[id]) {
|
if (!colorKeys[id]) {
|
||||||
var keys = Object.keys(colorKeys);
|
var keys = Object.keys(colorKeys);
|
||||||
keys.push(id);
|
keys.push(id);
|
||||||
keys.sort();
|
keys.sort(function(a, b) {
|
||||||
|
var pa = parseFloat(a);
|
||||||
|
var pb = parseFloat(b);
|
||||||
|
if (isNaN(pa) || isNaN(pb)) return a.localeCompare(b);
|
||||||
|
return pa - pb;
|
||||||
|
});
|
||||||
var colors = colorScale.colors(keys.length);
|
var colors = colorScale.colors(keys.length);
|
||||||
colorKeys = {};
|
colorKeys = {};
|
||||||
keys.forEach(function(key, index) {
|
keys.forEach(function(key, index) {
|
||||||
@ -195,6 +201,7 @@
|
|||||||
var reset = function(callsign, item) { item.setMap(); };
|
var reset = function(callsign, item) { item.setMap(); };
|
||||||
$.each(markers, reset);
|
$.each(markers, reset);
|
||||||
$.each(rectangles, reset);
|
$.each(rectangles, reset);
|
||||||
|
receiverMarker.setMap();
|
||||||
markers = {};
|
markers = {};
|
||||||
rectangles = {};
|
rectangles = {};
|
||||||
};
|
};
|
||||||
@ -222,12 +229,13 @@
|
|||||||
switch (json.type) {
|
switch (json.type) {
|
||||||
case "config":
|
case "config":
|
||||||
var config = json.value;
|
var config = json.value;
|
||||||
|
var receiverPos = {
|
||||||
|
lat: config.receiver_gps.lat,
|
||||||
|
lng: config.receiver_gps.lon
|
||||||
|
};
|
||||||
if (!map) $.getScript("https://maps.googleapis.com/maps/api/js?key=" + config.google_maps_api_key).done(function(){
|
if (!map) $.getScript("https://maps.googleapis.com/maps/api/js?key=" + config.google_maps_api_key).done(function(){
|
||||||
map = new google.maps.Map($('.openwebrx-map')[0], {
|
map = new google.maps.Map($('.openwebrx-map')[0], {
|
||||||
center: {
|
center: receiverPos,
|
||||||
lat: config.receiver_gps.lat,
|
|
||||||
lng: config.receiver_gps.lon
|
|
||||||
},
|
|
||||||
zoom: 5,
|
zoom: 5,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -240,7 +248,27 @@
|
|||||||
updateQueue = [];
|
updateQueue = [];
|
||||||
});
|
});
|
||||||
map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push($(".openwebrx-map-legend")[0]);
|
map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push($(".openwebrx-map-legend")[0]);
|
||||||
});
|
|
||||||
|
if (!receiverMarker) {
|
||||||
|
receiverMarker = new google.maps.Marker();
|
||||||
|
receiverMarker.addListener('click', function() {
|
||||||
|
showReceiverInfoWindow(receiverMarker);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
receiverMarker.setOptions({
|
||||||
|
map: map,
|
||||||
|
position: receiverPos,
|
||||||
|
title: config['receiver_name'],
|
||||||
|
config: config
|
||||||
|
});
|
||||||
|
}); else {
|
||||||
|
receiverMarker.setOptions({
|
||||||
|
map: map,
|
||||||
|
position: receiverPos,
|
||||||
|
title: config['receiver_name'],
|
||||||
|
config: config
|
||||||
|
});
|
||||||
|
}
|
||||||
retention_time = config.map_position_retention_time * 1000;
|
retention_time = config.map_position_retention_time * 1000;
|
||||||
break;
|
break;
|
||||||
case "update":
|
case "update":
|
||||||
@ -339,6 +367,15 @@
|
|||||||
infowindow.open(map, marker);
|
infowindow.open(map, marker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var showReceiverInfoWindow = function(marker) {
|
||||||
|
var infowindow = getInfoWindow()
|
||||||
|
infowindow.setContent(
|
||||||
|
'<h3>' + marker.config['receiver_name'] + '</h3>' +
|
||||||
|
'<div>Receiver location</div>'
|
||||||
|
);
|
||||||
|
infowindow.open(map, marker);
|
||||||
|
}
|
||||||
|
|
||||||
var getScale = function(lastseen) {
|
var getScale = function(lastseen) {
|
||||||
var age = new Date().getTime() - lastseen;
|
var age = new Date().getTime() - lastseen;
|
||||||
var scale = 1;
|
var scale = 1;
|
||||||
|
@ -292,7 +292,7 @@ function scale_canvas_mousemove(evt) {
|
|||||||
|
|
||||||
function frequency_container_mousemove(evt) {
|
function frequency_container_mousemove(evt) {
|
||||||
var frequency = center_freq + scale_offset_freq_from_px(evt.pageX);
|
var frequency = center_freq + scale_offset_freq_from_px(evt.pageX);
|
||||||
$('.webrx-mouse-freq').frequencyDisplay().setFrequency(frequency);
|
$('#openwebrx-panel-receiver').demodulatorPanel().setMouseFrequency(frequency);
|
||||||
}
|
}
|
||||||
|
|
||||||
function scale_canvas_end_drag(x) {
|
function scale_canvas_end_drag(x) {
|
||||||
@ -570,7 +570,7 @@ function canvas_mousemove(evt) {
|
|||||||
bookmarks.position();
|
bookmarks.position();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$('.webrx-mouse-freq').frequencyDisplay().setFrequency(canvas_get_frequency(relativeX));
|
$('#openwebrx-panel-receiver').demodulatorPanel().setMouseFrequency(canvas_get_frequency(relativeX));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -734,6 +734,8 @@ function on_ws_recv(evt) {
|
|||||||
currentprofile = config['sdr_id'] + '|' + config['profile_id'];
|
currentprofile = config['sdr_id'] + '|' + config['profile_id'];
|
||||||
$('#openwebrx-sdr-profiles-listbox').val(currentprofile);
|
$('#openwebrx-sdr-profiles-listbox').val(currentprofile);
|
||||||
|
|
||||||
|
$('#openwebrx-panel-receiver').demodulatorPanel().setFrequencyPrecision(config['frequency_display_precision']);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "secondary_config":
|
case "secondary_config":
|
||||||
var s = json['value'];
|
var s = json['value'];
|
||||||
@ -774,7 +776,7 @@ function on_ws_recv(evt) {
|
|||||||
$("#openwebrx-panel-js8-message").js8().pushMessage(json['value']);
|
$("#openwebrx-panel-js8-message").js8().pushMessage(json['value']);
|
||||||
break;
|
break;
|
||||||
case "wsjt_message":
|
case "wsjt_message":
|
||||||
update_wsjt_panel(json['value']);
|
$("#openwebrx-panel-wsjt-message").wsjtMessagePanel().pushMessage(json['value']);
|
||||||
break;
|
break;
|
||||||
case "dial_frequencies":
|
case "dial_frequencies":
|
||||||
var as_bookmarks = json['value'].map(function (d) {
|
var as_bookmarks = json['value'].map(function (d) {
|
||||||
@ -787,7 +789,7 @@ function on_ws_recv(evt) {
|
|||||||
bookmarks.replace_bookmarks(as_bookmarks, 'dial_frequencies');
|
bookmarks.replace_bookmarks(as_bookmarks, 'dial_frequencies');
|
||||||
break;
|
break;
|
||||||
case "aprs_data":
|
case "aprs_data":
|
||||||
update_packet_panel(json['value']);
|
$('#openwebrx-panel-packet-message').packetMessagePanel().pushMessage(json['value']);
|
||||||
break;
|
break;
|
||||||
case "bookmarks":
|
case "bookmarks":
|
||||||
bookmarks.replace_bookmarks(json['value'], "server");
|
bookmarks.replace_bookmarks(json['value'], "server");
|
||||||
@ -805,7 +807,7 @@ function on_ws_recv(evt) {
|
|||||||
divlog(json['value'], true);
|
divlog(json['value'], true);
|
||||||
break;
|
break;
|
||||||
case 'pocsag_data':
|
case 'pocsag_data':
|
||||||
update_pocsag_panel(json['value']);
|
$('#openwebrx-panel-pocsag-message').pocsagMessagePanel().pushMessage(json['value']);
|
||||||
break;
|
break;
|
||||||
case 'backoff':
|
case 'backoff':
|
||||||
divlog("Server is currently busy: " + json['reason'], true);
|
divlog("Server is currently busy: " + json['reason'], true);
|
||||||
@ -941,141 +943,6 @@ function update_metadata(meta) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function html_escape(input) {
|
|
||||||
return $('<div/>').text(input).html()
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_wsjt_panel(msg) {
|
|
||||||
var $b = $('#openwebrx-panel-wsjt-message').find('tbody');
|
|
||||||
var t = new Date(msg['timestamp']);
|
|
||||||
var pad = function (i) {
|
|
||||||
return ('' + i).padStart(2, "0");
|
|
||||||
};
|
|
||||||
var linkedmsg = msg['msg'];
|
|
||||||
var matches;
|
|
||||||
if (['FT8', 'JT65', 'JT9', 'FT4'].indexOf(msg['mode']) >= 0) {
|
|
||||||
matches = linkedmsg.match(/(.*\s[A-Z0-9]+\s)([A-R]{2}[0-9]{2})$/);
|
|
||||||
if (matches && matches[2] !== 'RR73') {
|
|
||||||
linkedmsg = html_escape(matches[1]) + '<a href="map?locator=' + matches[2] + '" target="openwebrx-map">' + matches[2] + '</a>';
|
|
||||||
} else {
|
|
||||||
linkedmsg = html_escape(linkedmsg);
|
|
||||||
}
|
|
||||||
} else if (msg['mode'] === 'WSPR') {
|
|
||||||
matches = linkedmsg.match(/([A-Z0-9]*\s)([A-R]{2}[0-9]{2})(\s[0-9]+)/);
|
|
||||||
if (matches) {
|
|
||||||
linkedmsg = html_escape(matches[1]) + '<a href="map?locator=' + matches[2] + '" target="openwebrx-map">' + matches[2] + '</a>' + html_escape(matches[3]);
|
|
||||||
} else {
|
|
||||||
linkedmsg = html_escape(linkedmsg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$b.append($(
|
|
||||||
'<tr data-timestamp="' + msg['timestamp'] + '">' +
|
|
||||||
'<td>' + pad(t.getUTCHours()) + pad(t.getUTCMinutes()) + pad(t.getUTCSeconds()) + '</td>' +
|
|
||||||
'<td class="decimal">' + msg['db'] + '</td>' +
|
|
||||||
'<td class="decimal">' + msg['dt'] + '</td>' +
|
|
||||||
'<td class="decimal freq">' + msg['freq'] + '</td>' +
|
|
||||||
'<td class="message">' + linkedmsg + '</td>' +
|
|
||||||
'</tr>'
|
|
||||||
));
|
|
||||||
$b.scrollTop($b[0].scrollHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
var digital_removal_interval;
|
|
||||||
|
|
||||||
// remove old wsjt messages in fixed intervals
|
|
||||||
function init_digital_removal_timer() {
|
|
||||||
if (digital_removal_interval) clearInterval(digital_removal_interval);
|
|
||||||
digital_removal_interval = setInterval(function () {
|
|
||||||
['#openwebrx-panel-wsjt-message', '#openwebrx-panel-packet-message'].forEach(function (root) {
|
|
||||||
var $elements = $(root + ' tbody tr');
|
|
||||||
// limit to 1000 entries in the list since browsers get laggy at some point
|
|
||||||
var toRemove = $elements.length - 1000;
|
|
||||||
if (toRemove <= 0) return;
|
|
||||||
$elements.slice(0, toRemove).remove();
|
|
||||||
});
|
|
||||||
}, 15000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_packet_panel(msg) {
|
|
||||||
var $b = $('#openwebrx-panel-packet-message').find('tbody');
|
|
||||||
var pad = function (i) {
|
|
||||||
return ('' + i).padStart(2, "0");
|
|
||||||
};
|
|
||||||
|
|
||||||
if (msg.type && msg.type === 'thirdparty' && msg.data) {
|
|
||||||
msg = msg.data;
|
|
||||||
}
|
|
||||||
var source = msg.source;
|
|
||||||
if (msg.type) {
|
|
||||||
if (msg.type === 'item') {
|
|
||||||
source = msg.item;
|
|
||||||
}
|
|
||||||
if (msg.type === 'object') {
|
|
||||||
source = msg.object;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var timestamp = '';
|
|
||||||
if (msg.timestamp) {
|
|
||||||
var t = new Date(msg.timestamp);
|
|
||||||
timestamp = pad(t.getUTCHours()) + pad(t.getUTCMinutes()) + pad(t.getUTCSeconds())
|
|
||||||
}
|
|
||||||
|
|
||||||
var link = '';
|
|
||||||
var classes = [];
|
|
||||||
var styles = {};
|
|
||||||
var overlay = '';
|
|
||||||
var stylesToString = function (s) {
|
|
||||||
return $.map(s, function (value, key) {
|
|
||||||
return key + ':' + value + ';'
|
|
||||||
}).join('')
|
|
||||||
};
|
|
||||||
if (msg.symbol) {
|
|
||||||
classes.push('aprs-symbol');
|
|
||||||
classes.push('aprs-symboltable-' + (msg.symbol.table === '/' ? 'normal' : 'alternate'));
|
|
||||||
styles['background-position-x'] = -(msg.symbol.index % 16) * 15 + 'px';
|
|
||||||
styles['background-position-y'] = -Math.floor(msg.symbol.index / 16) * 15 + 'px';
|
|
||||||
if (msg.symbol.table !== '/' && msg.symbol.table !== '\\') {
|
|
||||||
var s = {};
|
|
||||||
s['background-position-x'] = -(msg.symbol.tableindex % 16) * 15 + 'px';
|
|
||||||
s['background-position-y'] = -Math.floor(msg.symbol.tableindex / 16) * 15 + 'px';
|
|
||||||
overlay = '<div class="aprs-symbol aprs-symboltable-overlay" style="' + stylesToString(s) + '"></div>';
|
|
||||||
}
|
|
||||||
} else if (msg.lat && msg.lon) {
|
|
||||||
classes.push('openwebrx-maps-pin');
|
|
||||||
}
|
|
||||||
var attrs = [
|
|
||||||
'class="' + classes.join(' ') + '"',
|
|
||||||
'style="' + stylesToString(styles) + '"'
|
|
||||||
].join(' ');
|
|
||||||
if (msg.lat && msg.lon) {
|
|
||||||
link = '<a ' + attrs + ' href="map?callsign=' + source + '" target="openwebrx-map">' + overlay + '</a>';
|
|
||||||
} else {
|
|
||||||
link = '<div ' + attrs + '>' + overlay + '</div>'
|
|
||||||
}
|
|
||||||
|
|
||||||
$b.append($(
|
|
||||||
'<tr>' +
|
|
||||||
'<td>' + timestamp + '</td>' +
|
|
||||||
'<td class="callsign">' + source + '</td>' +
|
|
||||||
'<td class="coord">' + link + '</td>' +
|
|
||||||
'<td class="message">' + (msg.comment || msg.message || '') + '</td>' +
|
|
||||||
'</tr>'
|
|
||||||
));
|
|
||||||
$b.scrollTop($b[0].scrollHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_pocsag_panel(msg) {
|
|
||||||
var $b = $('#openwebrx-panel-pocsag-message').find('tbody');
|
|
||||||
$b.append($(
|
|
||||||
'<tr>' +
|
|
||||||
'<td class="address">' + msg.address + '</td>' +
|
|
||||||
'<td class="message">' + msg.message + '</td>' +
|
|
||||||
'</tr>'
|
|
||||||
));
|
|
||||||
$b.scrollTop($b[0].scrollHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clear_metadata() {
|
function clear_metadata() {
|
||||||
$(".openwebrx-meta-panel .openwebrx-meta-autoclear").text("");
|
$(".openwebrx-meta-panel .openwebrx-meta-autoclear").text("");
|
||||||
$(".openwebrx-meta-slot").removeClass("active").removeClass("sync");
|
$(".openwebrx-meta-slot").removeClass("active").removeClass("sync");
|
||||||
@ -1376,7 +1243,6 @@ function openwebrx_init() {
|
|||||||
secondary_demod_init();
|
secondary_demod_init();
|
||||||
digimodes_init();
|
digimodes_init();
|
||||||
initPanels();
|
initPanels();
|
||||||
$('.webrx-mouse-freq').frequencyDisplay();
|
|
||||||
$('#openwebrx-panel-receiver').demodulatorPanel();
|
$('#openwebrx-panel-receiver').demodulatorPanel();
|
||||||
window.addEventListener("resize", openwebrx_resize);
|
window.addEventListener("resize", openwebrx_resize);
|
||||||
bookmarks = new BookmarkBar();
|
bookmarks = new BookmarkBar();
|
||||||
@ -1588,7 +1454,10 @@ function secondary_demod_init() {
|
|||||||
.mousedown(secondary_demod_canvas_container_mousedown)
|
.mousedown(secondary_demod_canvas_container_mousedown)
|
||||||
.mouseenter(secondary_demod_canvas_container_mousein)
|
.mouseenter(secondary_demod_canvas_container_mousein)
|
||||||
.mouseleave(secondary_demod_canvas_container_mouseleave);
|
.mouseleave(secondary_demod_canvas_container_mouseleave);
|
||||||
init_digital_removal_timer();
|
$('#openwebrx-panel-wsjt-message').wsjtMessagePanel();
|
||||||
|
$('#openwebrx-panel-packet-message').packetMessagePanel();
|
||||||
|
$('#openwebrx-panel-pocsag-message').pocsagMessagePanel();
|
||||||
|
$('#openwebrx-panel-js8-message').js8();
|
||||||
}
|
}
|
||||||
|
|
||||||
function secondary_demod_push_data(x) {
|
function secondary_demod_push_data(x) {
|
||||||
|
@ -56,7 +56,8 @@ Support and info: https://groups.io/g/openwebrx
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Get error messages about unknown / unavailable features as soon as possible
|
# Get error messages about unknown / unavailable features as soon as possible
|
||||||
SdrService.loadProps()
|
# start up "always-on" sources right away
|
||||||
|
SdrService.getSources()
|
||||||
|
|
||||||
Services.start()
|
Services.start()
|
||||||
|
|
||||||
|
@ -186,8 +186,8 @@ class AudioWriter(object):
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
for line in decoder.stdout:
|
for line in decoder.stdout:
|
||||||
self.outputWriter.send((job.freq, line))
|
self.outputWriter.send((self.profile, job.freq, line))
|
||||||
except OSError:
|
except (OSError, AttributeError):
|
||||||
decoder.stdout.flush()
|
decoder.stdout.flush()
|
||||||
# TODO uncouple parsing from the output so that decodes can still go to the map and the spotters
|
# TODO uncouple parsing from the output so that decodes can still go to the map and the spotters
|
||||||
logger.debug("output has gone away while decoding job.")
|
logger.debug("output has gone away while decoding job.")
|
||||||
|
@ -124,6 +124,7 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient):
|
|||||||
"initial_squelch_level",
|
"initial_squelch_level",
|
||||||
"profile_id",
|
"profile_id",
|
||||||
"squelch_auto_margin",
|
"squelch_auto_margin",
|
||||||
|
"frequency_display_precision",
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, conn):
|
def __init__(self, conn):
|
||||||
@ -425,7 +426,12 @@ class MapConnection(OpenWebRxClient):
|
|||||||
super().__init__(conn)
|
super().__init__(conn)
|
||||||
|
|
||||||
pm = Config.get()
|
pm = Config.get()
|
||||||
self.write_config(pm.filter("google_maps_api_key", "receiver_gps", "map_position_retention_time").__dict__())
|
self.write_config(pm.filter(
|
||||||
|
"google_maps_api_key",
|
||||||
|
"receiver_gps",
|
||||||
|
"map_position_retention_time",
|
||||||
|
"receiver_name",
|
||||||
|
).__dict__())
|
||||||
|
|
||||||
Map.getSharedInstance().addClient(self)
|
Map.getSharedInstance().addClient(self)
|
||||||
|
|
||||||
@ -453,7 +459,7 @@ class WebSocketMessageHandler(object):
|
|||||||
self.handshake = {v[0]: "=".join(v[1:]) for v in map(lambda x: x.split("="), meta)}
|
self.handshake = {v[0]: "=".join(v[1:]) for v in map(lambda x: x.split("="), meta)}
|
||||||
|
|
||||||
conn.send("CLIENT DE SERVER server=openwebrx version={version}".format(version=openwebrx_version))
|
conn.send("CLIENT DE SERVER server=openwebrx version={version}".format(version=openwebrx_version))
|
||||||
logger.debug("client connection intitialized")
|
logger.debug("client connection initialized")
|
||||||
|
|
||||||
if "type" in self.handshake:
|
if "type" in self.handshake:
|
||||||
if self.handshake["type"] == "receiver":
|
if self.handshake["type"] == "receiver":
|
||||||
|
@ -126,6 +126,7 @@ class CompiledAssetsController(GzipMixin, ModificationAwareController):
|
|||||||
"lib/ProgressBar.js",
|
"lib/ProgressBar.js",
|
||||||
"lib/Measurement.js",
|
"lib/Measurement.js",
|
||||||
"lib/FrequencyDisplay.js",
|
"lib/FrequencyDisplay.js",
|
||||||
|
"lib/MessagePanel.js",
|
||||||
"lib/Js8Threads.js",
|
"lib/Js8Threads.js",
|
||||||
"lib/Modes.js",
|
"lib/Modes.js",
|
||||||
],
|
],
|
||||||
|
@ -28,7 +28,7 @@ class Js8Profiles(object):
|
|||||||
|
|
||||||
|
|
||||||
class Js8Profile(AudioChopperProfile, metaclass=ABCMeta):
|
class Js8Profile(AudioChopperProfile, metaclass=ABCMeta):
|
||||||
def decoding_depth(self, mode):
|
def decoding_depth(self):
|
||||||
pm = Config.get()
|
pm = Config.get()
|
||||||
# return global default
|
# return global default
|
||||||
if "js8_decoding_depth" in pm:
|
if "js8_decoding_depth" in pm:
|
||||||
@ -40,7 +40,7 @@ class Js8Profile(AudioChopperProfile, metaclass=ABCMeta):
|
|||||||
return "%y%m%d_%H%M%S"
|
return "%y%m%d_%H%M%S"
|
||||||
|
|
||||||
def decoder_commandline(self, file):
|
def decoder_commandline(self, file):
|
||||||
return ["js8", "--js8", "-b", self.get_sub_mode(), "-d", str(self.decoding_depth("js8")), file]
|
return ["js8", "--js8", "-b", self.get_sub_mode(), "-d", str(self.decoding_depth()), file]
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_sub_mode(self):
|
def get_sub_mode(self):
|
||||||
@ -85,7 +85,7 @@ class Js8Parser(Parser):
|
|||||||
def parse(self, messages):
|
def parse(self, messages):
|
||||||
for raw in messages:
|
for raw in messages:
|
||||||
try:
|
try:
|
||||||
freq, raw_msg = raw
|
profile, freq, raw_msg = raw
|
||||||
self.setDialFrequency(freq)
|
self.setDialFrequency(freq)
|
||||||
msg = raw_msg.decode().rstrip()
|
msg = raw_msg.decode().rstrip()
|
||||||
if Js8Parser.decoderRegex.match(msg):
|
if Js8Parser.decoderRegex.match(msg):
|
||||||
|
@ -81,11 +81,12 @@ class PskReporter(object):
|
|||||||
else:
|
else:
|
||||||
self.spotCounter.inc()
|
self.spotCounter.inc()
|
||||||
self.spots.append(spot)
|
self.spots.append(spot)
|
||||||
self.scheduleNextUpload()
|
self.scheduleNextUpload()
|
||||||
|
|
||||||
def upload(self):
|
def upload(self):
|
||||||
try:
|
try:
|
||||||
with self.spotLock:
|
with self.spotLock:
|
||||||
|
self.timer = None
|
||||||
spots = self.spots
|
spots = self.spots
|
||||||
self.spots = []
|
self.spots = []
|
||||||
|
|
||||||
@ -94,9 +95,6 @@ class PskReporter(object):
|
|||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Failed to upload spots")
|
logger.exception("Failed to upload spots")
|
||||||
|
|
||||||
self.timer = None
|
|
||||||
self.scheduleNextUpload()
|
|
||||||
|
|
||||||
def cancelTimer(self):
|
def cancelTimer(self):
|
||||||
if self.timer:
|
if self.timer:
|
||||||
self.timer.cancel()
|
self.timer.cancel()
|
||||||
@ -117,6 +115,8 @@ class Uploader(object):
|
|||||||
|
|
||||||
def getPackets(self, spots):
|
def getPackets(self, spots):
|
||||||
encoded = [self.encodeSpot(spot) for spot in spots]
|
encoded = [self.encodeSpot(spot) for spot in spots]
|
||||||
|
# filter out any erroneous encodes
|
||||||
|
encoded = [e for e in encoded if e is not None]
|
||||||
|
|
||||||
def chunks(l, n):
|
def chunks(l, n):
|
||||||
"""Yield successive n-sized chunks from l."""
|
"""Yield successive n-sized chunks from l."""
|
||||||
@ -152,40 +152,63 @@ class Uploader(object):
|
|||||||
return [len(s)] + list(s.encode("utf-8"))
|
return [len(s)] + list(s.encode("utf-8"))
|
||||||
|
|
||||||
def encodeSpot(self, spot):
|
def encodeSpot(self, spot):
|
||||||
return bytes(
|
try:
|
||||||
self.encodeString(spot["callsign"])
|
return bytes(
|
||||||
+ list(int(spot["freq"]).to_bytes(4, "big"))
|
self.encodeString(spot["callsign"])
|
||||||
+ list(int(spot["db"]).to_bytes(1, "big", signed=True))
|
+ list(int(spot["freq"]).to_bytes(4, "big"))
|
||||||
+ self.encodeString(spot["mode"])
|
+ list(int(spot["db"]).to_bytes(1, "big", signed=True))
|
||||||
+ self.encodeString(spot["locator"])
|
+ self.encodeString(spot["mode"])
|
||||||
# informationsource. 1 means "automatically extracted
|
+ self.encodeString(spot["locator"])
|
||||||
+ [0x01]
|
# informationsource. 1 means "automatically extracted
|
||||||
+ list(int(spot["timestamp"] / 1000).to_bytes(4, "big"))
|
+ [0x01]
|
||||||
)
|
+ list(int(spot["timestamp"] / 1000).to_bytes(4, "big"))
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error while encoding spot for pskreporter")
|
||||||
|
return None
|
||||||
|
|
||||||
def getReceiverInformationHeader(self):
|
def getReceiverInformationHeader(self):
|
||||||
|
pm = Config.get()
|
||||||
|
with_antenna = "pskreporter_antenna_information" in pm and pm["pskreporter_antenna_information"] is not None
|
||||||
|
num_fields = 4 if with_antenna else 3
|
||||||
|
length = 12 + num_fields * 8
|
||||||
return bytes(
|
return bytes(
|
||||||
# id, length
|
# id
|
||||||
[0x00, 0x03, 0x00, 0x24]
|
[0x00, 0x03]
|
||||||
|
# length
|
||||||
|
+ list(length.to_bytes(2, 'big'))
|
||||||
+ Uploader.receieverDelimiter
|
+ Uploader.receieverDelimiter
|
||||||
# number of fields
|
# number of fields
|
||||||
+ [0x00, 0x03, 0x00, 0x00]
|
+ list(num_fields.to_bytes(2, 'big'))
|
||||||
|
# padding
|
||||||
|
+ [0x00, 0x00]
|
||||||
# receiverCallsign
|
# receiverCallsign
|
||||||
+ [0x80, 0x02, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F]
|
+ [0x80, 0x02, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F]
|
||||||
# receiverLocator
|
# receiverLocator
|
||||||
+ [0x80, 0x04, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F]
|
+ [0x80, 0x04, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F]
|
||||||
# decodingSoftware
|
# decodingSoftware
|
||||||
+ [0x80, 0x08, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F]
|
+ [0x80, 0x08, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F]
|
||||||
|
# antennaInformation
|
||||||
|
+ (
|
||||||
|
[0x80, 0x09, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F] if with_antenna else []
|
||||||
|
)
|
||||||
# padding
|
# padding
|
||||||
+ [0x00, 0x00]
|
+ [0x00, 0x00]
|
||||||
)
|
)
|
||||||
|
|
||||||
def getReceiverInformation(self):
|
def getReceiverInformation(self):
|
||||||
pm = Config.get()
|
pm = Config.get()
|
||||||
callsign = pm["pskreporter_callsign"]
|
bodyFields = [
|
||||||
locator = Locator.fromCoordinates(pm["receiver_gps"])
|
# callsign
|
||||||
decodingSoftware = "OpenWebRX " + openwebrx_version
|
pm["pskreporter_callsign"],
|
||||||
body = [b for s in [callsign, locator, decodingSoftware] for b in self.encodeString(s)]
|
# locator
|
||||||
|
Locator.fromCoordinates(pm["receiver_gps"]),
|
||||||
|
# decodingSoftware
|
||||||
|
"OpenWebRX " + openwebrx_version,
|
||||||
|
]
|
||||||
|
if "pskreporter_antenna_information" in pm and pm["pskreporter_antenna_information"] is not None:
|
||||||
|
bodyFields += [pm["pskreporter_antenna_information"]]
|
||||||
|
body = [b for s in bodyFields for b in self.encodeString(s)]
|
||||||
body = self.pad(body, 4)
|
body = self.pad(body, 4)
|
||||||
body = bytes(Uploader.receieverDelimiter + list((len(body) + 4).to_bytes(2, "big")) + body)
|
body = bytes(Uploader.receieverDelimiter + list((len(body) + 4).to_bytes(2, "big")) + body)
|
||||||
return body
|
return body
|
||||||
|
@ -13,7 +13,7 @@ class SdrService(object):
|
|||||||
lastPort = None
|
lastPort = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def loadProps():
|
def _loadProps():
|
||||||
if SdrService.sdrProps is None:
|
if SdrService.sdrProps is None:
|
||||||
pm = Config.get()
|
pm = Config.get()
|
||||||
featureDetector = FeatureDetector()
|
featureDetector = FeatureDetector()
|
||||||
@ -60,7 +60,6 @@ class SdrService(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getSource(id):
|
def getSource(id):
|
||||||
SdrService.loadProps()
|
|
||||||
sources = SdrService.getSources()
|
sources = SdrService.getSources()
|
||||||
if not sources:
|
if not sources:
|
||||||
return None
|
return None
|
||||||
@ -70,7 +69,7 @@ class SdrService(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getSources():
|
def getSources():
|
||||||
SdrService.loadProps()
|
SdrService._loadProps()
|
||||||
for id in SdrService.sdrProps.keys():
|
for id in SdrService.sdrProps.keys():
|
||||||
if not id in SdrService.sources:
|
if not id in SdrService.sources:
|
||||||
props = SdrService.sdrProps[id]
|
props = SdrService.sdrProps[id]
|
||||||
|
152
owrx/wsjt.py
152
owrx/wsjt.py
@ -14,8 +14,9 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class WsjtProfile(AudioChopperProfile, metaclass=ABCMeta):
|
class WsjtProfile(AudioChopperProfile, metaclass=ABCMeta):
|
||||||
def decoding_depth(self, mode):
|
def decoding_depth(self):
|
||||||
pm = Config.get()
|
pm = Config.get()
|
||||||
|
mode = self.getMode().lower()
|
||||||
# mode-specific setting?
|
# mode-specific setting?
|
||||||
if "wsjt_decoding_depths" in pm and mode in pm["wsjt_decoding_depths"]:
|
if "wsjt_decoding_depths" in pm and mode in pm["wsjt_decoding_depths"]:
|
||||||
return pm["wsjt_decoding_depths"][mode]
|
return pm["wsjt_decoding_depths"][mode]
|
||||||
@ -25,64 +26,76 @@ class WsjtProfile(AudioChopperProfile, metaclass=ABCMeta):
|
|||||||
# default when no setting is provided
|
# default when no setting is provided
|
||||||
return 3
|
return 3
|
||||||
|
|
||||||
|
def getTimestampFormat(self):
|
||||||
|
if self.getInterval() < 60:
|
||||||
|
return "%H%M%S"
|
||||||
|
return "%H%M"
|
||||||
|
|
||||||
|
def getFileTimestampFormat(self):
|
||||||
|
return "%y%m%d_" + self.getTimestampFormat()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def getMode(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Ft8Profile(WsjtProfile):
|
class Ft8Profile(WsjtProfile):
|
||||||
def getInterval(self):
|
def getInterval(self):
|
||||||
return 15
|
return 15
|
||||||
|
|
||||||
def getFileTimestampFormat(self):
|
|
||||||
return "%y%m%d_%H%M%S"
|
|
||||||
|
|
||||||
def decoder_commandline(self, file):
|
def decoder_commandline(self, file):
|
||||||
return ["jt9", "--ft8", "-d", str(self.decoding_depth("ft8")), file]
|
return ["jt9", "--ft8", "-d", str(self.decoding_depth()), file]
|
||||||
|
|
||||||
|
def getMode(self):
|
||||||
|
return "FT8"
|
||||||
|
|
||||||
|
|
||||||
class WsprProfile(WsjtProfile):
|
class WsprProfile(WsjtProfile):
|
||||||
def getInterval(self):
|
def getInterval(self):
|
||||||
return 120
|
return 120
|
||||||
|
|
||||||
def getFileTimestampFormat(self):
|
|
||||||
return "%y%m%d_%H%M"
|
|
||||||
|
|
||||||
def decoder_commandline(self, file):
|
def decoder_commandline(self, file):
|
||||||
cmd = ["wsprd"]
|
cmd = ["wsprd"]
|
||||||
if self.decoding_depth("wspr") > 1:
|
if self.decoding_depth() > 1:
|
||||||
cmd += ["-d"]
|
cmd += ["-d"]
|
||||||
cmd += [file]
|
cmd += [file]
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
def getMode(self):
|
||||||
|
return "WSPR"
|
||||||
|
|
||||||
|
|
||||||
class Jt65Profile(WsjtProfile):
|
class Jt65Profile(WsjtProfile):
|
||||||
def getInterval(self):
|
def getInterval(self):
|
||||||
return 60
|
return 60
|
||||||
|
|
||||||
def getFileTimestampFormat(self):
|
|
||||||
return "%y%m%d_%H%M"
|
|
||||||
|
|
||||||
def decoder_commandline(self, file):
|
def decoder_commandline(self, file):
|
||||||
return ["jt9", "--jt65", "-d", str(self.decoding_depth("jt65")), file]
|
return ["jt9", "--jt65", "-d", str(self.decoding_depth()), file]
|
||||||
|
|
||||||
|
def getMode(self):
|
||||||
|
return "JT65"
|
||||||
|
|
||||||
|
|
||||||
class Jt9Profile(WsjtProfile):
|
class Jt9Profile(WsjtProfile):
|
||||||
def getInterval(self):
|
def getInterval(self):
|
||||||
return 60
|
return 60
|
||||||
|
|
||||||
def getFileTimestampFormat(self):
|
|
||||||
return "%y%m%d_%H%M"
|
|
||||||
|
|
||||||
def decoder_commandline(self, file):
|
def decoder_commandline(self, file):
|
||||||
return ["jt9", "--jt9", "-d", str(self.decoding_depth("jt9")), file]
|
return ["jt9", "--jt9", "-d", str(self.decoding_depth()), file]
|
||||||
|
|
||||||
|
def getMode(self):
|
||||||
|
return "JT9"
|
||||||
|
|
||||||
|
|
||||||
class Ft4Profile(WsjtProfile):
|
class Ft4Profile(WsjtProfile):
|
||||||
def getInterval(self):
|
def getInterval(self):
|
||||||
return 7.5
|
return 7.5
|
||||||
|
|
||||||
def getFileTimestampFormat(self):
|
|
||||||
return "%y%m%d_%H%M%S"
|
|
||||||
|
|
||||||
def decoder_commandline(self, file):
|
def decoder_commandline(self, file):
|
||||||
return ["jt9", "--ft4", "-d", str(self.decoding_depth("ft4")), file]
|
return ["jt9", "--ft4", "-d", str(self.decoding_depth()), file]
|
||||||
|
|
||||||
|
def getMode(self):
|
||||||
|
return "FT4"
|
||||||
|
|
||||||
|
|
||||||
class Fst4Profile(WsjtProfile):
|
class Fst4Profile(WsjtProfile):
|
||||||
@ -94,13 +107,11 @@ class Fst4Profile(WsjtProfile):
|
|||||||
def getInterval(self):
|
def getInterval(self):
|
||||||
return self.interval
|
return self.interval
|
||||||
|
|
||||||
def getFileTimestampFormat(self):
|
|
||||||
if self.interval < 60:
|
|
||||||
return "%y%m%d_%H%M%S"
|
|
||||||
return "%y%m%d_%H%M"
|
|
||||||
|
|
||||||
def decoder_commandline(self, file):
|
def decoder_commandline(self, file):
|
||||||
return ["jt9", "--fst4", "-p", str(self.interval), "-d", str(self.decoding_depth("fst4")), file]
|
return ["jt9", "--fst4", "-p", str(self.interval), "-d", str(self.decoding_depth()), file]
|
||||||
|
|
||||||
|
def getMode(self):
|
||||||
|
return "FST4"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getEnabledProfiles():
|
def getEnabledProfiles():
|
||||||
@ -118,13 +129,11 @@ class Fst4wProfile(WsjtProfile):
|
|||||||
def getInterval(self):
|
def getInterval(self):
|
||||||
return self.interval
|
return self.interval
|
||||||
|
|
||||||
def getFileTimestampFormat(self):
|
|
||||||
if self.interval < 60:
|
|
||||||
return "%y%m%d_%H%M%S"
|
|
||||||
return "%y%m%d_%H%M"
|
|
||||||
|
|
||||||
def decoder_commandline(self, file):
|
def decoder_commandline(self, file):
|
||||||
return ["jt9", "--fst4w", "-p", str(self.interval), "-d", str(self.decoding_depth("fst4w")), file]
|
return ["jt9", "--fst4w", "-p", str(self.interval), "-d", str(self.decoding_depth()), file]
|
||||||
|
|
||||||
|
def getMode(self):
|
||||||
|
return "FST4W"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getEnabledProfiles():
|
def getEnabledProfiles():
|
||||||
@ -134,12 +143,10 @@ class Fst4wProfile(WsjtProfile):
|
|||||||
|
|
||||||
|
|
||||||
class WsjtParser(Parser):
|
class WsjtParser(Parser):
|
||||||
modes = {"~": "FT8", "#": "JT65", "@": "JT9", "+": "FT4", "`": "FST4"}
|
|
||||||
|
|
||||||
def parse(self, messages):
|
def parse(self, messages):
|
||||||
for data in messages:
|
for data in messages:
|
||||||
try:
|
try:
|
||||||
freq, raw_msg = data
|
profile, freq, raw_msg = data
|
||||||
self.setDialFrequency(freq)
|
self.setDialFrequency(freq)
|
||||||
msg = raw_msg.decode().rstrip()
|
msg = raw_msg.decode().rstrip()
|
||||||
# known debug messages we know to skip
|
# known debug messages we know to skip
|
||||||
@ -148,19 +155,20 @@ class WsjtParser(Parser):
|
|||||||
if msg.startswith(" EOF on input file"):
|
if msg.startswith(" EOF on input file"):
|
||||||
return
|
return
|
||||||
|
|
||||||
modes = list(WsjtParser.modes.keys())
|
mode = profile.getMode()
|
||||||
if msg[21] in modes or msg[19] in modes:
|
if mode == "WSPR":
|
||||||
decoder = Jt9Decoder()
|
decoder = WsprDecoder(profile)
|
||||||
else:
|
else:
|
||||||
decoder = WsprDecoder()
|
decoder = Jt9Decoder(profile)
|
||||||
out = decoder.parse(msg, freq)
|
out = decoder.parse(msg, freq)
|
||||||
if "mode" in out:
|
out["mode"] = mode
|
||||||
self.pushDecode(out["mode"])
|
|
||||||
if "callsign" in out and "locator" in out:
|
self.pushDecode(mode)
|
||||||
Map.getSharedInstance().updateLocation(
|
if "callsign" in out and "locator" in out:
|
||||||
out["callsign"], LocatorLocation(out["locator"]), out["mode"], self.band
|
Map.getSharedInstance().updateLocation(
|
||||||
)
|
out["callsign"], LocatorLocation(out["locator"]), mode, self.band
|
||||||
PskReporter.getSharedInstance().spot(out)
|
)
|
||||||
|
PskReporter.getSharedInstance().spot(out)
|
||||||
|
|
||||||
self.handler.write_wsjt_message(out)
|
self.handler.write_wsjt_message(out)
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
@ -187,13 +195,21 @@ class WsjtParser(Parser):
|
|||||||
|
|
||||||
|
|
||||||
class Decoder(ABC):
|
class Decoder(ABC):
|
||||||
locator_pattern = re.compile(".*\\s([A-Z0-9]{2,})(\\sR)?\\s([A-R]{2}[0-9]{2})$")
|
locator_pattern = re.compile(".*\\s([A-Z0-9/]{2,})(\\sR)?\\s([A-R]{2}[0-9]{2})$")
|
||||||
|
|
||||||
def parse_timestamp(self, instring, dateformat):
|
def __init__(self, profile):
|
||||||
ts = datetime.strptime(instring, dateformat)
|
self.profile = profile
|
||||||
return int(
|
|
||||||
datetime.combine(datetime.utcnow().date(), ts.time()).replace(tzinfo=timezone.utc).timestamp() * 1000
|
def parse_timestamp(self, instring):
|
||||||
)
|
dateformat = self.profile.getTimestampFormat()
|
||||||
|
remain = instring[len(dateformat) + 1:]
|
||||||
|
try:
|
||||||
|
ts = datetime.strptime(instring[0:len(dateformat)], dateformat)
|
||||||
|
return remain, int(
|
||||||
|
datetime.combine(datetime.utcnow().date(), ts.time()).replace(tzinfo=timezone.utc).timestamp() * 1000
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
return remain, None
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def parse(self, msg, dial_freq):
|
def parse(self, msg, dial_freq):
|
||||||
@ -219,18 +235,7 @@ class Jt9Decoder(Decoder):
|
|||||||
# '0003 -4 0.4 1762 # CQ R2ABM KO85'
|
# '0003 -4 0.4 1762 # CQ R2ABM KO85'
|
||||||
# fst4 sample
|
# fst4 sample
|
||||||
# '**** -23 0.6 3023 ` <...> <...> R 591631 BI53PV'
|
# '**** -23 0.6 3023 ` <...> <...> R 591631 BI53PV'
|
||||||
modes = list(WsjtParser.modes.keys())
|
msg, timestamp = self.parse_timestamp(msg)
|
||||||
if msg[19] in modes:
|
|
||||||
dateformat = "%H%M"
|
|
||||||
else:
|
|
||||||
dateformat = "%H%M%S"
|
|
||||||
try:
|
|
||||||
timestamp = self.parse_timestamp(msg[0 : len(dateformat)], dateformat)
|
|
||||||
except ValueError:
|
|
||||||
timestamp = None
|
|
||||||
msg = msg[len(dateformat) + 1:]
|
|
||||||
modeChar = msg[14:15]
|
|
||||||
mode = WsjtParser.modes[modeChar] if modeChar in WsjtParser.modes else "unknown"
|
|
||||||
wsjt_msg = msg[17:53].strip()
|
wsjt_msg = msg[17:53].strip()
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
@ -238,7 +243,6 @@ class Jt9Decoder(Decoder):
|
|||||||
"db": float(msg[0:3]),
|
"db": float(msg[0:3]),
|
||||||
"dt": float(msg[4:8]),
|
"dt": float(msg[4:8]),
|
||||||
"freq": dial_freq + int(msg[9:13]),
|
"freq": dial_freq + int(msg[9:13]),
|
||||||
"mode": mode,
|
|
||||||
"msg": wsjt_msg,
|
"msg": wsjt_msg,
|
||||||
}
|
}
|
||||||
result.update(self.parseMessage(wsjt_msg))
|
result.update(self.parseMessage(wsjt_msg))
|
||||||
@ -246,20 +250,20 @@ class Jt9Decoder(Decoder):
|
|||||||
|
|
||||||
|
|
||||||
class WsprDecoder(Decoder):
|
class WsprDecoder(Decoder):
|
||||||
wspr_splitter_pattern = re.compile("([A-Z0-9]*)\\s([A-R]{2}[0-9]{2})\\s([0-9]+)")
|
wspr_splitter_pattern = re.compile("([A-Z0-9/]*)\\s([A-R]{2}[0-9]{2})\\s([0-9]+)")
|
||||||
|
|
||||||
def parse(self, msg, dial_freq):
|
def parse(self, msg, dial_freq):
|
||||||
# wspr sample
|
# wspr sample
|
||||||
# '2600 -24 0.4 0.001492 -1 G8AXA JO01 33'
|
# '2600 -24 0.4 0.001492 -1 G8AXA JO01 33'
|
||||||
# '0052 -29 2.6 0.001486 0 G02CWT IO92 23'
|
# '0052 -29 2.6 0.001486 0 G02CWT IO92 23'
|
||||||
wsjt_msg = msg[29:].strip()
|
msg, timestamp = self.parse_timestamp(msg)
|
||||||
|
wsjt_msg = msg[24:].strip()
|
||||||
result = {
|
result = {
|
||||||
"timestamp": self.parse_timestamp(msg[0:4], "%H%M"),
|
"timestamp": timestamp,
|
||||||
"db": float(msg[5:8]),
|
"db": float(msg[0:3]),
|
||||||
"dt": float(msg[9:13]),
|
"dt": float(msg[4:8]),
|
||||||
"freq": dial_freq + int(float(msg[14:24]) * 1e6),
|
"freq": dial_freq + int(float(msg[10:20]) * 1e6),
|
||||||
"drift": int(msg[25:28]),
|
"drift": int(msg[20:23]),
|
||||||
"mode": "WSPR",
|
|
||||||
"msg": wsjt_msg,
|
"msg": wsjt_msg,
|
||||||
}
|
}
|
||||||
result.update(self.parseMessage(wsjt_msg))
|
result.update(self.parseMessage(wsjt_msg))
|
||||||
|
Loading…
Reference in New Issue
Block a user