diff --git a/htdocs/css/admin.css b/htdocs/css/admin.css
index 21e9279c..02e9a8ad 100644
--- a/htdocs/css/admin.css
+++ b/htdocs/css/admin.css
@@ -160,3 +160,7 @@ h1 {
.imageupload.is-invalid ~ .invalid-feedback {
display: block;
}
+
+.device-log-messages {
+ max-height: 500px;
+}
\ No newline at end of file
diff --git a/htdocs/features.js b/htdocs/features.js
index fef2817e..0add3b43 100644
--- a/htdocs/features.js
+++ b/htdocs/features.js
@@ -1,5 +1,5 @@
$(function(){
- var converter = new showdown.Converter();
+ var converter = new showdown.Converter({openLinksInNewWindow: true});
$.ajax('api/features').done(function(data){
var $table = $('table.features');
$.each(data, function(name, details) {
diff --git a/htdocs/include/header.include.html b/htdocs/include/header.include.html
index aa04045d..7953bba7 100644
--- a/htdocs/include/header.include.html
+++ b/htdocs/include/header.include.html
@@ -30,7 +30,7 @@
${photo_desc}
-
-
+
+
diff --git a/htdocs/lib/AprsMarker.js b/htdocs/lib/AprsMarker.js
index f6720c10..381c4ea9 100644
--- a/htdocs/lib/AprsMarker.js
+++ b/htdocs/lib/AprsMarker.js
@@ -2,6 +2,18 @@ function AprsMarker() {}
AprsMarker.prototype = new google.maps.OverlayView();
+AprsMarker.prototype.isFacingEast = function(symbol) {
+ var candidates = ''
+ if (symbol.table === '/') {
+ // primary table
+ candidates = '(*<=>CFPUXYabefghjkpsuv[';
+ } else {
+ // alternate table
+ candidates = 'hkluv';
+ }
+ return candidates.includes(symbol.symbol);
+};
+
AprsMarker.prototype.draw = function() {
var div = this.div;
var overlay = this.overlay;
@@ -16,9 +28,15 @@ AprsMarker.prototype.draw = function() {
}
if (this.course) {
- if (this.course > 180) {
+ if (this.symbol && !this.isFacingEast(this.symbol)) {
+ // assume symbol points to the north
+ div.style.transform = 'rotate(' + this.course + ' deg)';
+ } else if (this.course > 180) {
+ // symbol is pointing east
+ // don't rotate more than 180 degrees, rather mirror
div.style.transform = 'scalex(-1) rotate(' + (270 - this.course) + 'deg)'
} else {
+ // symbol is pointing east
div.style.transform = 'rotate(' + (this.course - 90) + 'deg)';
}
} else {
@@ -70,7 +88,7 @@ AprsMarker.prototype.onAdd = function() {
div.appendChild(overlay);
var self = this;
- google.maps.event.addDomListener(div, "click", function(event) {
+ div.addEventListener("click", function(event) {
event.stopPropagation();
google.maps.event.trigger(self, "click", event);
});
@@ -79,7 +97,7 @@ AprsMarker.prototype.onAdd = function() {
panes.overlayImage.appendChild(div);
};
-AprsMarker.prototype.remove = function() {
+AprsMarker.prototype.onRemove = function() {
if (this.div) {
this.div.parentNode.removeChild(this.div);
this.div = null;
diff --git a/htdocs/lib/AudioEngine.js b/htdocs/lib/AudioEngine.js
index efdb453c..7bd5d3eb 100644
--- a/htdocs/lib/AudioEngine.js
+++ b/htdocs/lib/AudioEngine.js
@@ -413,7 +413,9 @@ ImaAdpcmCodec.prototype.reset = function() {
this.synchronized = 0;
this.syncWord = "SYNC";
this.syncCounter = 0;
- this.skip = 0;
+ this.phase = 0;
+ this.syncBuffer = new Uint8Array(4);
+ this.syncBufferIndex = 0;
};
ImaAdpcmCodec.imaIndexTable = [ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8 ];
@@ -441,38 +443,45 @@ ImaAdpcmCodec.prototype.decode = function(data) {
ImaAdpcmCodec.prototype.decodeWithSync = function(data) {
var output = new Int16Array(data.length * 2);
- var index = this.skip;
var oi = 0;
- while (index < data.length) {
- while (this.synchronized < 4 && index < data.length) {
- if (data[index] === this.syncWord.charCodeAt(this.synchronized)) {
- this.synchronized++;
- } else {
- this.synchronized = 0;
- }
- index++;
- if (this.synchronized === 4) {
- if (index + 4 < data.length) {
- var syncData = new Int16Array(data.buffer.slice(index, index + 4));
+ for (var index = 0; index < data.length; index++) {
+ switch (this.phase) {
+ case 0:
+ // search for sync word
+ if (data[index] !== this.syncWord.charCodeAt(this.synchronized++)) {
+ // reset if data is unexpected
+ this.synchronized = 0;
+ }
+ // if sync word has been found pass on to next phase
+ if (this.synchronized === 4) {
+ this.syncBufferIndex = 0;
+ this.phase = 1;
+ }
+ break;
+ case 1:
+ // read codec runtime data from stream
+ this.syncBuffer[this.syncBufferIndex++] = data[index];
+ // if data is complete, apply and pass on to next phase
+ if (this.syncBufferIndex === 4) {
+ var syncData = new Int16Array(this.syncBuffer.buffer);
this.stepIndex = syncData[0];
this.predictor = syncData[1];
+ this.syncCounter = 1000;
+ this.phase = 2;
}
- this.syncCounter = 1000;
- index += 4;
break;
- }
- }
- while (index < data.length) {
- if (this.syncCounter-- < 0) {
- this.synchronized = 0;
+ case 2:
+ // decode actual audio data
+ output[oi++] = this.decodeNibble(data[index] & 0x0F);
+ output[oi++] = this.decodeNibble(data[index] >> 4);
+ // if the next sync keyword is due, reset and return to phase 0
+ if (this.syncCounter-- === 0) {
+ this.synchronized = 0;
+ this.phase = 0;
+ }
break;
- }
- output[oi++] = this.decodeNibble(data[index] & 0x0F);
- output[oi++] = this.decodeNibble(data[index] >> 4);
- index++;
}
}
- this.skip = index - data.length;
return output.slice(0, oi);
};
diff --git a/htdocs/lib/Demodulator.js b/htdocs/lib/Demodulator.js
index 1b94e5aa..9ba349e2 100644
--- a/htdocs/lib/Demodulator.js
+++ b/htdocs/lib/Demodulator.js
@@ -347,6 +347,13 @@ Demodulator.prototype.setBandpass = function(bandpass) {
this.set();
};
+Demodulator.prototype.disableBandpass = function() {
+ delete this.bandpass;
+ this.low_cut = null;
+ this.high_cut = null;
+ this.set()
+}
+
Demodulator.prototype.setLowCut = function(low_cut) {
this.low_cut = low_cut;
this.set();
diff --git a/htdocs/lib/DemodulatorPanel.js b/htdocs/lib/DemodulatorPanel.js
index fd4a9add..b0d546d4 100644
--- a/htdocs/lib/DemodulatorPanel.js
+++ b/htdocs/lib/DemodulatorPanel.js
@@ -91,7 +91,7 @@ DemodulatorPanel.prototype.setMode = function(requestedModulation, underlyingMod
return;
}
- if (this.mode === mode && (!underlyingModulation || this.underlyingModulation === underlyingModulation)) {
+ if (this.mode === mode && this.underlyingModulation === underlyingModulation) {
return;
}
if (!mode.isAvailable()) {
@@ -101,12 +101,9 @@ DemodulatorPanel.prototype.setMode = function(requestedModulation, underlyingMod
var modulation;
if (mode.type === 'digimode') {
- if (underlyingModulation) {
- modulation = underlyingModulation
- } else {
- modulation = mode.underlying[0];
- }
+ modulation = underlyingModulation = underlyingModulation || mode.underlying[0];
} else {
+ underlyingModulation = undefined;
modulation = mode.modulation;
}
@@ -138,8 +135,12 @@ DemodulatorPanel.prototype.setMode = function(requestedModulation, underlyingMod
if (mode.type === 'digimode') {
this.demodulator.set_secondary_demod(mode.modulation);
- if (mode.bandpass) {
- this.demodulator.setBandpass(mode.bandpass);
+ var uMode = Modes.findByModulation(underlyingModulation);
+ var bandpass = mode.bandpass || (uMode && uMode.bandpass);
+ if (bandpass) {
+ this.demodulator.setBandpass(bandpass);
+ } else {
+ this.demodulator.disableBandpass();
}
} else {
this.demodulator.set_secondary_demod(false);
@@ -147,7 +148,7 @@ DemodulatorPanel.prototype.setMode = function(requestedModulation, underlyingMod
this.demodulator.start();
this.mode = mode;
- this.underlyingModulation = modulation;
+ this.underlyingModulation = underlyingModulation;
this.updateButtons();
this.updatePanels();
@@ -161,7 +162,8 @@ DemodulatorPanel.prototype.disableDigiMode = function() {
DemodulatorPanel.prototype.updatePanels = function() {
var modulation = this.getDemodulator().get_secondary_demod();
$('#openwebrx-panel-digimodes').attr('data-mode', modulation);
- toggle_panel("openwebrx-panel-digimodes", !!modulation);
+ var mode = Modes.findByModulation(modulation);
+ toggle_panel("openwebrx-panel-digimodes", modulation && (!mode || mode.secondaryFft));
toggle_panel("openwebrx-panel-wsjt-message", ["ft8", "wspr", "jt65", "jt9", "ft4", "fst4", "fst4w", "q65", "msk144"].indexOf(modulation) >= 0);
toggle_panel("openwebrx-panel-js8-message", modulation == "js8");
toggle_panel("openwebrx-panel-packet-message", ["packet", "ais"].indexOf(modulation) >= 0);
@@ -393,6 +395,6 @@ DemodulatorPanel.prototype.setTuningPrecision = function(precision) {
$.fn.demodulatorPanel = function(){
if (!this.data('panel')) {
this.data('panel', new DemodulatorPanel(this));
- };
+ }
return this.data('panel');
};
diff --git a/htdocs/lib/Modes.js b/htdocs/lib/Modes.js
index c68466ae..ee82b7d7 100644
--- a/htdocs/lib/Modes.js
+++ b/htdocs/lib/Modes.js
@@ -43,6 +43,7 @@ var Mode = function(json){
}
if (this.type === 'digimode') {
this.underlying = json.underlying;
+ this.secondaryFft = json.secondaryFft;
}
};
diff --git a/htdocs/lib/settings/LogMessages.js b/htdocs/lib/settings/LogMessages.js
new file mode 100644
index 00000000..ea2683ea
--- /dev/null
+++ b/htdocs/lib/settings/LogMessages.js
@@ -0,0 +1,5 @@
+$.fn.logMessages = function() {
+ $.each(this, function(){
+ $(this).scrollTop(this.scrollHeight);
+ });
+};
\ No newline at end of file
diff --git a/htdocs/settings.js b/htdocs/settings.js
index 3a071b5b..b1bc7361 100644
--- a/htdocs/settings.js
+++ b/htdocs/settings.js
@@ -8,4 +8,5 @@ $(function(){
$('.optional-section').optionalSection();
$('#scheduler').schedulerInput();
$('.exponential-input').exponentialInput();
+ $('.device-log-messages').logMessages();
});
\ No newline at end of file
diff --git a/openwebrx.conf b/openwebrx.conf
index 32a0f77c..9bbfe806 100644
--- a/openwebrx.conf
+++ b/openwebrx.conf
@@ -1,9 +1,11 @@
[core]
data_directory = /var/lib/openwebrx
temporary_directory = /tmp
+log_level = INFO
[web]
port = 8073
+ipv6 = true
[aprs]
# path to the aprs symbols repository (get it here: https://github.com/hessu/aprs-symbols)