Merge branch 'develop' into pycsdr
This commit is contained in:
commit
3e69c71ed5
@ -340,6 +340,27 @@ aprs_igate_beacon = False
|
|||||||
# path to the aprs symbols repository (get it here: https://github.com/hessu/aprs-symbols)
|
# path to the aprs symbols repository (get it here: https://github.com/hessu/aprs-symbols)
|
||||||
aprs_symbols_path = "/usr/share/aprs-symbols/png"
|
aprs_symbols_path = "/usr/share/aprs-symbols/png"
|
||||||
|
|
||||||
|
# Uncomment the following to customize gateway beacon details reported to the aprs network
|
||||||
|
# Plese see Dire Wolf's documentation on PBEACON configuration for complete details:
|
||||||
|
# https://github.com/wb2osz/direwolf/raw/master/doc/User-Guide.pdf
|
||||||
|
|
||||||
|
# Symbol in its two-character form as specified by the APRS spec at http://www.aprs.org/symbols/symbols-new.txt
|
||||||
|
# Default: Receive only IGate (do not send msgs back to RF)
|
||||||
|
# aprs_igate_symbol = "R&"
|
||||||
|
|
||||||
|
# Custom comment about igate
|
||||||
|
# Default: OpenWebRX APRS gateway
|
||||||
|
# aprs_igate_comment = "OpenWebRX APRS gateway"
|
||||||
|
|
||||||
|
# Antenna Height and Gain details
|
||||||
|
# Unspecified by default
|
||||||
|
# Antenna height above average terrain (HAAT) in meters
|
||||||
|
# aprs_igate_height = "5"
|
||||||
|
# Antenna gain in dBi
|
||||||
|
# aprs_igate_gain = "0"
|
||||||
|
# Antenna direction (N, NE, E, SE, S, SW, W, NW). Omnidirectional by default
|
||||||
|
# aprs_igate_dir = "NE"
|
||||||
|
|
||||||
# === PSK Reporter setting ===
|
# === PSK Reporter setting ===
|
||||||
# enable this if you want to upload all ft8, ft4 etc spots to pskreporter.info
|
# enable this if you want to upload all ft8, ft4 etc spots to pskreporter.info
|
||||||
# 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
|
||||||
|
@ -551,13 +551,25 @@ img.openwebrx-mirror-img
|
|||||||
-khtml-user-select: none;
|
-khtml-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
-ms-user-select: none;
|
-ms-user-select: none;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.openwebrx-progressbar-bar
|
.openwebrx-progressbar-bar {
|
||||||
{
|
background-color: #00aba6;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: -100%;
|
||||||
|
transition-property: left,background-color;
|
||||||
|
transition-duration: 1s;
|
||||||
|
transition-timing-function: ease-in-out;
|
||||||
|
transform: translateZ(0);
|
||||||
|
will-change: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openwebrx-progressbar--over .openwebrx-progressbar-bar {
|
||||||
|
background-color: #ff6262;
|
||||||
}
|
}
|
||||||
|
|
||||||
.openwebrx-progressbar-text
|
.openwebrx-progressbar-text
|
||||||
@ -566,6 +578,7 @@ img.openwebrx-mirror-img
|
|||||||
left:0px;
|
left:0px;
|
||||||
top:4px;
|
top:4px;
|
||||||
width: inherit;
|
width: inherit;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#openwebrx-panel-status
|
#openwebrx-panel-status
|
||||||
|
@ -4,6 +4,7 @@ function DemodulatorPanel(el) {
|
|||||||
self.demodulator = null;
|
self.demodulator = null;
|
||||||
self.mode = null;
|
self.mode = null;
|
||||||
self.squelchMargin = 10;
|
self.squelchMargin = 10;
|
||||||
|
self.initialParams = {};
|
||||||
|
|
||||||
var displayEl = el.find('.webrx-actual-freq')
|
var displayEl = el.find('.webrx-actual-freq')
|
||||||
this.tuneableFrequencyDisplay = displayEl.tuneableFrequencyDisplay();
|
this.tuneableFrequencyDisplay = displayEl.tuneableFrequencyDisplay();
|
||||||
@ -180,7 +181,7 @@ DemodulatorPanel.prototype.collectParams = function() {
|
|||||||
squelch_level: -150,
|
squelch_level: -150,
|
||||||
mod: 'nfm'
|
mod: 'nfm'
|
||||||
}
|
}
|
||||||
return $.extend(new Object(), defaults, this.initialParams || {}, this.transformHashParams(this.parseHash()));
|
return $.extend(new Object(), defaults, this.initialParams, this.transformHashParams(this.parseHash()));
|
||||||
};
|
};
|
||||||
|
|
||||||
DemodulatorPanel.prototype.startDemodulator = function() {
|
DemodulatorPanel.prototype.startDemodulator = function() {
|
||||||
@ -206,7 +207,7 @@ DemodulatorPanel.prototype._apply = function(params) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
DemodulatorPanel.prototype.setInitialParams = function(params) {
|
DemodulatorPanel.prototype.setInitialParams = function(params) {
|
||||||
this.initialParams = params;
|
$.extend(this.initialParams, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
DemodulatorPanel.prototype.onHashChange = function() {
|
DemodulatorPanel.prototype.onHashChange = function() {
|
||||||
|
@ -3,7 +3,6 @@ ProgressBar = function(el) {
|
|||||||
this.$innerText = $('<span class="openwebrx-progressbar-text">' + this.getDefaultText() + '</span>');
|
this.$innerText = $('<span class="openwebrx-progressbar-text">' + this.getDefaultText() + '</span>');
|
||||||
this.$innerBar = $('<div class="openwebrx-progressbar-bar"></div>');
|
this.$innerBar = $('<div class="openwebrx-progressbar-bar"></div>');
|
||||||
this.$el.empty().append(this.$innerText, this.$innerBar);
|
this.$el.empty().append(this.$innerText, this.$innerBar);
|
||||||
this.$innerBar.css('width', '0%');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ProgressBar.prototype.getDefaultText = function() {
|
ProgressBar.prototype.getDefaultText = function() {
|
||||||
@ -19,7 +18,7 @@ ProgressBar.prototype.set = function(val, text, over) {
|
|||||||
ProgressBar.prototype.setValue = function(val) {
|
ProgressBar.prototype.setValue = function(val) {
|
||||||
if (val < 0) val = 0;
|
if (val < 0) val = 0;
|
||||||
if (val > 1) val = 1;
|
if (val > 1) val = 1;
|
||||||
this.$innerBar.stop().animate({width: val * 100 + '%'}, 700);
|
this.$innerBar.css({left: (val - 1) * 100 + '%'});
|
||||||
};
|
};
|
||||||
|
|
||||||
ProgressBar.prototype.setText = function(text) {
|
ProgressBar.prototype.setText = function(text) {
|
||||||
@ -27,7 +26,7 @@ ProgressBar.prototype.setText = function(text) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ProgressBar.prototype.setOver = function(over) {
|
ProgressBar.prototype.setOver = function(over) {
|
||||||
this.$innerBar.css('backgroundColor', (over) ? "#ff6262" : "#00aba6");
|
this.$el[over ? 'addClass' : 'removeClass']('openwebrx-progressbar--over');
|
||||||
};
|
};
|
||||||
|
|
||||||
AudioBufferProgressBar = function(el) {
|
AudioBufferProgressBar = function(el) {
|
||||||
|
@ -314,14 +314,16 @@ function scale_px_from_freq(f, range) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function get_visible_freq_range() {
|
function get_visible_freq_range() {
|
||||||
var out = {};
|
if (!bandwidth) return false;
|
||||||
var fcalc = function (x) {
|
var fcalc = function (x) {
|
||||||
var canvasWidth = waterfallWidth() * zoom_levels[zoom_level];
|
var canvasWidth = waterfallWidth() * zoom_levels[zoom_level];
|
||||||
return Math.round(((-zoom_offset_px + x) / canvasWidth) * bandwidth) + (center_freq - bandwidth / 2);
|
return Math.round(((-zoom_offset_px + x) / canvasWidth) * bandwidth) + (center_freq - bandwidth / 2);
|
||||||
};
|
};
|
||||||
out.start = fcalc(0);
|
var out = {
|
||||||
out.center = fcalc(waterfallWidth() / 2);
|
start: fcalc(0),
|
||||||
out.end = fcalc(waterfallWidth());
|
center: fcalc(waterfallWidth() / 2),
|
||||||
|
end: fcalc(waterfallWidth()),
|
||||||
|
}
|
||||||
out.bw = out.end - out.start;
|
out.bw = out.end - out.start;
|
||||||
out.hps = out.bw / waterfallWidth();
|
out.hps = out.bw / waterfallWidth();
|
||||||
return out;
|
return out;
|
||||||
@ -426,6 +428,7 @@ var range;
|
|||||||
function mkscale() {
|
function mkscale() {
|
||||||
//clear the lower part of the canvas (where frequency scale resides; the upper part is used by filter envelopes):
|
//clear the lower part of the canvas (where frequency scale resides; the upper part is used by filter envelopes):
|
||||||
range = get_visible_freq_range();
|
range = get_visible_freq_range();
|
||||||
|
if (!range) return;
|
||||||
mkenvelopes(range); //when scale changes we will always have to redraw filter envelopes, too
|
mkenvelopes(range); //when scale changes we will always have to redraw filter envelopes, too
|
||||||
scale_ctx.clearRect(0, 22, scale_ctx.canvas.width, scale_ctx.canvas.height - 22);
|
scale_ctx.clearRect(0, 22, scale_ctx.canvas.width, scale_ctx.canvas.height - 22);
|
||||||
scale_ctx.strokeStyle = "#fff";
|
scale_ctx.strokeStyle = "#fff";
|
||||||
@ -442,9 +445,7 @@ function mkscale() {
|
|||||||
};
|
};
|
||||||
var last_large;
|
var last_large;
|
||||||
var x;
|
var x;
|
||||||
for (; ;) {
|
while ((x = scale_px_from_freq(marker_hz, range)) <= window.innerWidth) {
|
||||||
x = scale_px_from_freq(marker_hz, range);
|
|
||||||
if (x > window.innerWidth) break;
|
|
||||||
scale_ctx.beginPath();
|
scale_ctx.beginPath();
|
||||||
scale_ctx.moveTo(x, 22);
|
scale_ctx.moveTo(x, 22);
|
||||||
if (marker_hz % spacing.params.large_marker_per_hz === 0) { //large marker
|
if (marker_hz % spacing.params.large_marker_per_hz === 0) { //large marker
|
||||||
@ -700,41 +701,60 @@ function on_ws_recv(evt) {
|
|||||||
switch (json.type) {
|
switch (json.type) {
|
||||||
case "config":
|
case "config":
|
||||||
var config = json['value'];
|
var config = json['value'];
|
||||||
waterfall_colors = buildWaterfallColors(config['waterfall_colors']);
|
if ('waterfall_colors' in config)
|
||||||
waterfall_min_level_default = config['waterfall_min_level'];
|
waterfall_colors = buildWaterfallColors(config['waterfall_colors']);
|
||||||
waterfall_max_level_default = config['waterfall_max_level'];
|
if ('waterfall_min_level' in config)
|
||||||
waterfall_auto_level_margin = config['waterfall_auto_level_margin'];
|
waterfall_min_level_default = config['waterfall_min_level'];
|
||||||
|
if ('waterfall_max_level' in config)
|
||||||
|
waterfall_max_level_default = config['waterfall_max_level'];
|
||||||
|
if ('waterfall_auto_level_margin' in config)
|
||||||
|
waterfall_auto_level_margin = config['waterfall_auto_level_margin'];
|
||||||
waterfallColorsDefault();
|
waterfallColorsDefault();
|
||||||
|
|
||||||
var initial_demodulator_params = {
|
var initial_demodulator_params = {};
|
||||||
mod: config['start_mod'],
|
if ('start_mod' in config)
|
||||||
offset_frequency: config['start_offset_freq'],
|
initial_demodulator_params['mod'] = config['start_mod'];
|
||||||
squelch_level: Number.isInteger(config['initial_squelch_level']) ? config['initial_squelch_level'] : -150
|
if ('start_offset_freq' in config)
|
||||||
};
|
initial_demodulator_params['offset_frequency'] = config['start_offset_freq'];
|
||||||
|
if ('initial_squelch_level' in config)
|
||||||
|
initial_demodulator_params['squelch_level'] = Number.isInteger(config['initial_squelch_level']) ? config['initial_squelch_level'] : -150;
|
||||||
|
|
||||||
bandwidth = config['samp_rate'];
|
if ('samp_rate' in config)
|
||||||
center_freq = config['center_freq'];
|
bandwidth = config['samp_rate'];
|
||||||
fft_size = config['fft_size'];
|
if ('center_freq' in config)
|
||||||
var audio_compression = config['audio_compression'];
|
center_freq = config['center_freq'];
|
||||||
audioEngine.setCompression(audio_compression);
|
if ('fft_size' in config)
|
||||||
divlog("Audio stream is " + ((audio_compression === "adpcm") ? "compressed" : "uncompressed") + ".");
|
fft_size = config['fft_size'];
|
||||||
fft_compression = config['fft_compression'];
|
if ('audio_compression' in config) {
|
||||||
divlog("FFT stream is " + ((fft_compression === "adpcm") ? "compressed" : "uncompressed") + ".");
|
var audio_compression = config['audio_compression'];
|
||||||
$('#openwebrx-bar-clients').progressbar().setMaxClients(config['max_clients']);
|
audioEngine.setCompression(audio_compression);
|
||||||
|
divlog("Audio stream is " + ((audio_compression === "adpcm") ? "compressed" : "uncompressed") + ".");
|
||||||
|
}
|
||||||
|
if ('fft_compression' in config) {
|
||||||
|
fft_compression = config['fft_compression'];
|
||||||
|
divlog("FFT stream is " + ((fft_compression === "adpcm") ? "compressed" : "uncompressed") + ".");
|
||||||
|
}
|
||||||
|
if ('max_clients' in config)
|
||||||
|
$('#openwebrx-bar-clients').progressbar().setMaxClients(config['max_clients']);
|
||||||
|
|
||||||
waterfall_init();
|
waterfall_init();
|
||||||
|
|
||||||
var demodulatorPanel = $('#openwebrx-panel-receiver').demodulatorPanel();
|
var demodulatorPanel = $('#openwebrx-panel-receiver').demodulatorPanel();
|
||||||
demodulatorPanel.setCenterFrequency(center_freq);
|
demodulatorPanel.setCenterFrequency(center_freq);
|
||||||
demodulatorPanel.setInitialParams(initial_demodulator_params);
|
demodulatorPanel.setInitialParams(initial_demodulator_params);
|
||||||
demodulatorPanel.setSquelchMargin(config['squelch_auto_margin']);
|
if ('squelch_auto_margin' in config)
|
||||||
|
demodulatorPanel.setSquelchMargin(config['squelch_auto_margin']);
|
||||||
bookmarks.loadLocalBookmarks();
|
bookmarks.loadLocalBookmarks();
|
||||||
|
|
||||||
|
if ('sdr_id' in config && 'profile_id' in config) {
|
||||||
|
currentprofile = config['sdr_id'] + '|' + config['profile_id'];
|
||||||
|
$('#openwebrx-sdr-profiles-listbox').val(currentprofile);
|
||||||
|
}
|
||||||
|
|
||||||
waterfall_clear();
|
waterfall_clear();
|
||||||
|
|
||||||
currentprofile = config['sdr_id'] + '|' + config['profile_id'];
|
if ('frequency_display_precision' in config)
|
||||||
$('#openwebrx-sdr-profiles-listbox').val(currentprofile);
|
$('#openwebrx-panel-receiver').demodulatorPanel().setFrequencyPrecision(config['frequency_display_precision']);
|
||||||
|
|
||||||
$('#openwebrx-panel-receiver').demodulatorPanel().setFrequencyPrecision(config['frequency_display_precision']);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "secondary_config":
|
case "secondary_config":
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from owrx.modes import Modes
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@ -12,7 +13,15 @@ class Band(object):
|
|||||||
self.upper_bound = dict["upper_bound"]
|
self.upper_bound = dict["upper_bound"]
|
||||||
self.frequencies = []
|
self.frequencies = []
|
||||||
if "frequencies" in dict:
|
if "frequencies" in dict:
|
||||||
|
availableModes = [mode.modulation for mode in Modes.getAvailableModes()]
|
||||||
for (mode, freqs) in dict["frequencies"].items():
|
for (mode, freqs) in dict["frequencies"].items():
|
||||||
|
if mode not in availableModes:
|
||||||
|
logger.info(
|
||||||
|
"Modulation \"{mode}\" is not available, bandplan bookmark will not be displayed".format(
|
||||||
|
mode=mode
|
||||||
|
)
|
||||||
|
)
|
||||||
|
continue
|
||||||
if not isinstance(freqs, list):
|
if not isinstance(freqs, list):
|
||||||
freqs = [freqs]
|
freqs = [freqs]
|
||||||
for f in freqs:
|
for f in freqs:
|
||||||
@ -22,8 +31,8 @@ class Band(object):
|
|||||||
mode=mode, frequency=f, band=self.name
|
mode=mode, frequency=f, band=self.name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
continue
|
||||||
self.frequencies.append({"mode": mode, "frequency": f})
|
self.frequencies.append({"mode": mode, "frequency": f})
|
||||||
|
|
||||||
def inBand(self, freq):
|
def inBand(self, freq):
|
||||||
return self.lower_bound <= freq <= self.upper_bound
|
return self.lower_bound <= freq <= self.upper_bound
|
||||||
|
@ -12,6 +12,7 @@ from owrx.bookmarks import Bookmarks
|
|||||||
from owrx.map import Map
|
from owrx.map import Map
|
||||||
from owrx.property import PropertyStack
|
from owrx.property import PropertyStack
|
||||||
from owrx.modes import Modes, DigitalMode
|
from owrx.modes import Modes, DigitalMode
|
||||||
|
from owrx.config import Config
|
||||||
from queue import Queue, Full, Empty
|
from queue import Queue, Full, Empty
|
||||||
from js8py import Js8Frame
|
from js8py import Js8Frame
|
||||||
from abc import ABC, ABCMeta, abstractmethod
|
from abc import ABC, ABCMeta, abstractmethod
|
||||||
@ -108,22 +109,26 @@ class OpenWebRxClient(Client, metaclass=ABCMeta):
|
|||||||
|
|
||||||
|
|
||||||
class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient):
|
class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient):
|
||||||
config_keys = [
|
sdr_config_keys = [
|
||||||
"waterfall_colors",
|
"waterfall_min_level",
|
||||||
"waterfall_min_level",
|
"waterfall_min_level",
|
||||||
"waterfall_max_level",
|
"waterfall_max_level",
|
||||||
"waterfall_auto_level_margin",
|
|
||||||
"samp_rate",
|
"samp_rate",
|
||||||
"fft_size",
|
|
||||||
"audio_compression",
|
|
||||||
"fft_compression",
|
|
||||||
"max_clients",
|
|
||||||
"start_mod",
|
"start_mod",
|
||||||
"start_freq",
|
"start_freq",
|
||||||
"center_freq",
|
"center_freq",
|
||||||
"initial_squelch_level",
|
"initial_squelch_level",
|
||||||
"profile_id",
|
"profile_id",
|
||||||
"squelch_auto_margin",
|
"squelch_auto_margin",
|
||||||
|
]
|
||||||
|
|
||||||
|
global_config_keys = [
|
||||||
|
"waterfall_colors",
|
||||||
|
"waterfall_auto_level_margin",
|
||||||
|
"fft_size",
|
||||||
|
"audio_compression",
|
||||||
|
"fft_compression",
|
||||||
|
"max_clients",
|
||||||
"frequency_display_precision",
|
"frequency_display_precision",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -132,7 +137,7 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient):
|
|||||||
|
|
||||||
self.dsp = None
|
self.dsp = None
|
||||||
self.sdr = None
|
self.sdr = None
|
||||||
self.configSub = None
|
self.sdrConfigSubs = []
|
||||||
self.connectionProperties = {}
|
self.connectionProperties = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -142,6 +147,10 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient):
|
|||||||
self.close()
|
self.close()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
globalConfig = Config.get().filter(*OpenWebRxReceiverClient.global_config_keys)
|
||||||
|
self.globalConfigSub = globalConfig.wire(self.write_config)
|
||||||
|
self.write_config(globalConfig.__dict__())
|
||||||
|
|
||||||
self.setSdr()
|
self.setSdr()
|
||||||
|
|
||||||
features = FeatureDetector().feature_availability()
|
features = FeatureDetector().feature_availability()
|
||||||
@ -154,6 +163,10 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient):
|
|||||||
|
|
||||||
CpuUsageThread.getSharedInstance().add_client(self)
|
CpuUsageThread.getSharedInstance().add_client(self)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if hasattr(self, "globalConfigSub"):
|
||||||
|
self.globalConfigSub.cancel()
|
||||||
|
|
||||||
def onStateChange(self, state):
|
def onStateChange(self, state):
|
||||||
if state == SdrSource.STATE_RUNNING:
|
if state == SdrSource.STATE_RUNNING:
|
||||||
self.handleSdrAvailable()
|
self.handleSdrAvailable()
|
||||||
@ -231,9 +244,8 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient):
|
|||||||
|
|
||||||
self.stopDsp()
|
self.stopDsp()
|
||||||
|
|
||||||
if self.configSub is not None:
|
while self.sdrConfigSubs:
|
||||||
self.configSub.cancel()
|
self.sdrConfigSubs.pop().cancel()
|
||||||
self.configSub = None
|
|
||||||
|
|
||||||
if self.sdr is not None:
|
if self.sdr is not None:
|
||||||
self.sdr.removeClient(self)
|
self.sdr.removeClient(self)
|
||||||
@ -248,31 +260,38 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient):
|
|||||||
self.sdr.addClient(self)
|
self.sdr.addClient(self)
|
||||||
|
|
||||||
def handleSdrAvailable(self):
|
def handleSdrAvailable(self):
|
||||||
# send initial config
|
|
||||||
self.getDsp().setProperties(self.connectionProperties)
|
self.getDsp().setProperties(self.connectionProperties)
|
||||||
|
|
||||||
stack = PropertyStack()
|
stack = PropertyStack()
|
||||||
stack.addLayer(0, self.sdr.getProps())
|
stack.addLayer(0, self.sdr.getProps())
|
||||||
stack.addLayer(1, Config.get())
|
stack.addLayer(1, Config.get())
|
||||||
configProps = stack.filter(*OpenWebRxReceiverClient.config_keys)
|
configProps = stack.filter(*OpenWebRxReceiverClient.sdr_config_keys)
|
||||||
|
|
||||||
def sendConfig(key, value):
|
def sendConfig(changes=None):
|
||||||
config = configProps.__dict__()
|
if changes is None:
|
||||||
# TODO mathematical properties? hmmmm
|
config = configProps.__dict__()
|
||||||
config["start_offset_freq"] = configProps["start_freq"] - configProps["center_freq"]
|
else:
|
||||||
# TODO this is a hack to support multiple sdrs
|
config = changes
|
||||||
config["sdr_id"] = self.sdr.getId()
|
if changes is None or "start_freq" in changes or "center_freq" in changes:
|
||||||
|
config["start_offset_freq"] = configProps["start_freq"] - configProps["center_freq"]
|
||||||
|
if changes is None or "profile_id" in changes:
|
||||||
|
config["sdr_id"] = self.sdr.getId()
|
||||||
self.write_config(config)
|
self.write_config(config)
|
||||||
|
|
||||||
|
def sendBookmarks(changes=None):
|
||||||
cf = configProps["center_freq"]
|
cf = configProps["center_freq"]
|
||||||
srh = configProps["samp_rate"] / 2
|
srh = configProps["samp_rate"] / 2
|
||||||
frequencyRange = (cf - srh, cf + srh)
|
frequencyRange = (cf - srh, cf + srh)
|
||||||
self.write_dial_frequendies(Bandplan.getSharedInstance().collectDialFrequencies(frequencyRange))
|
self.write_dial_frequencies(Bandplan.getSharedInstance().collectDialFrequencies(frequencyRange))
|
||||||
bookmarks = [b.__dict__() for b in Bookmarks.getSharedInstance().getBookmarks(frequencyRange)]
|
bookmarks = [b.__dict__() for b in Bookmarks.getSharedInstance().getBookmarks(frequencyRange)]
|
||||||
self.write_bookmarks(bookmarks)
|
self.write_bookmarks(bookmarks)
|
||||||
|
|
||||||
self.configSub = configProps.wire(sendConfig)
|
self.sdrConfigSubs.append(configProps.wire(sendConfig))
|
||||||
sendConfig(None, None)
|
self.sdrConfigSubs.append(stack.filter("center_freq", "samp_rate").wire(sendBookmarks))
|
||||||
|
|
||||||
|
# send initial config
|
||||||
|
sendConfig()
|
||||||
|
sendBookmarks()
|
||||||
self.__sendProfiles()
|
self.__sendProfiles()
|
||||||
|
|
||||||
self.sdr.addSpectrumClient(self)
|
self.sdr.addSpectrumClient(self)
|
||||||
@ -289,9 +308,8 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient):
|
|||||||
self.stopDsp()
|
self.stopDsp()
|
||||||
CpuUsageThread.getSharedInstance().remove_client(self)
|
CpuUsageThread.getSharedInstance().remove_client(self)
|
||||||
ClientRegistry.getSharedInstance().removeClient(self)
|
ClientRegistry.getSharedInstance().removeClient(self)
|
||||||
if self.configSub is not None:
|
while self.sdrConfigSubs:
|
||||||
self.configSub.cancel()
|
self.sdrConfigSubs.pop().cancel()
|
||||||
self.configSub = None
|
|
||||||
super().close()
|
super().close()
|
||||||
|
|
||||||
def stopDsp(self):
|
def stopDsp(self):
|
||||||
@ -368,7 +386,7 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient):
|
|||||||
def write_wsjt_message(self, message):
|
def write_wsjt_message(self, message):
|
||||||
self.send({"type": "wsjt_message", "value": message})
|
self.send({"type": "wsjt_message", "value": message})
|
||||||
|
|
||||||
def write_dial_frequendies(self, frequencies):
|
def write_dial_frequencies(self, frequencies):
|
||||||
self.send({"type": "dial_frequencies", "value": frequencies})
|
self.send({"type": "dial_frequencies", "value": frequencies})
|
||||||
|
|
||||||
def write_bookmarks(self, bookmarks):
|
def write_bookmarks(self, bookmarks):
|
||||||
|
@ -7,11 +7,13 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class CpuUsageThread(threading.Thread):
|
class CpuUsageThread(threading.Thread):
|
||||||
sharedInstance = None
|
sharedInstance = None
|
||||||
|
creationLock = threading.Lock()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getSharedInstance():
|
def getSharedInstance():
|
||||||
if CpuUsageThread.sharedInstance is None:
|
with CpuUsageThread.creationLock:
|
||||||
CpuUsageThread.sharedInstance = CpuUsageThread()
|
if CpuUsageThread.sharedInstance is None:
|
||||||
|
CpuUsageThread.sharedInstance = CpuUsageThread()
|
||||||
return CpuUsageThread.sharedInstance
|
return CpuUsageThread.sharedInstance
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -23,6 +25,7 @@ class CpuUsageThread(threading.Thread):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
logger.debug("cpu usage thread starting up")
|
||||||
while self.doRun:
|
while self.doRun:
|
||||||
try:
|
try:
|
||||||
cpu_usage = self.get_cpu_usage()
|
cpu_usage = self.get_cpu_usage()
|
||||||
|
71
owrx/dsp.py
71
owrx/dsp.py
@ -28,35 +28,41 @@ class DspManager(csdr.output, SdrSourceEventClient):
|
|||||||
|
|
||||||
self.props = PropertyStack()
|
self.props = PropertyStack()
|
||||||
# local demodulator properties not forwarded to the sdr
|
# local demodulator properties not forwarded to the sdr
|
||||||
self.props.addLayer(0, PropertyLayer().filter(
|
self.props.addLayer(
|
||||||
"output_rate",
|
0,
|
||||||
"hd_output_rate",
|
PropertyLayer().filter(
|
||||||
"squelch_level",
|
"output_rate",
|
||||||
"secondary_mod",
|
"hd_output_rate",
|
||||||
"low_cut",
|
"squelch_level",
|
||||||
"high_cut",
|
"secondary_mod",
|
||||||
"offset_freq",
|
"low_cut",
|
||||||
"mod",
|
"high_cut",
|
||||||
"secondary_offset_freq",
|
"offset_freq",
|
||||||
"dmr_filter",
|
"mod",
|
||||||
))
|
"secondary_offset_freq",
|
||||||
|
"dmr_filter",
|
||||||
|
),
|
||||||
|
)
|
||||||
# properties that we inherit from the sdr
|
# properties that we inherit from the sdr
|
||||||
self.props.addLayer(1, self.sdrSource.getProps().filter(
|
self.props.addLayer(
|
||||||
"audio_compression",
|
1,
|
||||||
"fft_compression",
|
self.sdrSource.getProps().filter(
|
||||||
"digimodes_fft_size",
|
"audio_compression",
|
||||||
"csdr_dynamic_bufsize",
|
"fft_compression",
|
||||||
"csdr_print_bufsizes",
|
"digimodes_fft_size",
|
||||||
"csdr_through",
|
"csdr_dynamic_bufsize",
|
||||||
"digimodes_enable",
|
"csdr_print_bufsizes",
|
||||||
"samp_rate",
|
"csdr_through",
|
||||||
"digital_voice_unvoiced_quality",
|
"digimodes_enable",
|
||||||
"temporary_directory",
|
"samp_rate",
|
||||||
"center_freq",
|
"digital_voice_unvoiced_quality",
|
||||||
"start_mod",
|
"temporary_directory",
|
||||||
"start_freq",
|
"center_freq",
|
||||||
"wfm_deemphasis_tau",
|
"start_mod",
|
||||||
))
|
"start_freq",
|
||||||
|
"wfm_deemphasis_tau",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
self.dsp = csdr.dsp(self)
|
self.dsp = csdr.dsp(self)
|
||||||
self.dsp.nc_port = self.sdrSource.getPort()
|
self.dsp.nc_port = self.sdrSource.getPort()
|
||||||
@ -71,8 +77,13 @@ class DspManager(csdr.output, SdrSourceEventClient):
|
|||||||
bpf[1] = cut
|
bpf[1] = cut
|
||||||
self.dsp.set_bpf(*bpf)
|
self.dsp.set_bpf(*bpf)
|
||||||
|
|
||||||
def set_dial_freq(key, value):
|
def set_dial_freq(changes):
|
||||||
if self.props["center_freq"] is None or self.props["offset_freq"] is None:
|
if (
|
||||||
|
"center_freq" not in self.props
|
||||||
|
or self.props["center_freq"] is None
|
||||||
|
or "offset_freq" not in self.props
|
||||||
|
or self.props["offset_freq"] is None
|
||||||
|
):
|
||||||
return
|
return
|
||||||
freq = self.props["center_freq"] + self.props["offset_freq"]
|
freq = self.props["center_freq"] + self.props["offset_freq"]
|
||||||
for parser in self.parsers.values():
|
for parser in self.parsers.values():
|
||||||
|
34
owrx/kiss.py
34
owrx/kiss.py
@ -11,6 +11,7 @@ FESC = 0xDB
|
|||||||
TFEND = 0xDC
|
TFEND = 0xDC
|
||||||
TFESC = 0xDD
|
TFESC = 0xDD
|
||||||
|
|
||||||
|
FEET_PER_METER = 3.28084
|
||||||
|
|
||||||
class DirewolfConfig(object):
|
class DirewolfConfig(object):
|
||||||
def getConfig(self, port, is_service):
|
def getConfig(self, port, is_service):
|
||||||
@ -39,6 +40,7 @@ IGLOGIN {callsign} {password}
|
|||||||
)
|
)
|
||||||
|
|
||||||
if pm["aprs_igate_beacon"]:
|
if pm["aprs_igate_beacon"]:
|
||||||
|
#Format beacon lat/lon
|
||||||
lat = pm["receiver_gps"]["lat"]
|
lat = pm["receiver_gps"]["lat"]
|
||||||
lon = pm["receiver_gps"]["lon"]
|
lon = pm["receiver_gps"]["lon"]
|
||||||
direction_ns = "N" if lat > 0 else "S"
|
direction_ns = "N" if lat > 0 else "S"
|
||||||
@ -48,11 +50,33 @@ IGLOGIN {callsign} {password}
|
|||||||
lat = "{0:02d}^{1:05.2f}{2}".format(int(lat), (lat - int(lat)) * 60, direction_ns)
|
lat = "{0:02d}^{1:05.2f}{2}".format(int(lat), (lat - int(lat)) * 60, direction_ns)
|
||||||
lon = "{0:03d}^{1:05.2f}{2}".format(int(lon), (lon - int(lon)) * 60, direction_we)
|
lon = "{0:03d}^{1:05.2f}{2}".format(int(lon), (lon - int(lon)) * 60, direction_we)
|
||||||
|
|
||||||
config += """
|
#Format beacon details
|
||||||
PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat={lat} long={lon} comment="OpenWebRX APRS gateway"
|
symbol = str(pm["aprs_igate_symbol"]) if "aprs_igate_symbol" in pm else "R&"
|
||||||
""".format(
|
gain = "GAIN=" + str(pm["aprs_igate_gain"]) if "aprs_igate_gain" in pm else ""
|
||||||
lat=lat, lon=lon
|
adir = "DIR=" + str(pm["aprs_igate_dir"]) if "aprs_igate_dir" in pm else ""
|
||||||
)
|
comment = str(pm["aprs_igate_comment"]) if "aprs_igate_comment" in pm else "\"OpenWebRX APRS gateway\""
|
||||||
|
|
||||||
|
#Convert height from meters to feet if specified
|
||||||
|
height = ""
|
||||||
|
if "aprs_igate_height" in pm:
|
||||||
|
try:
|
||||||
|
height_m = float(pm["aprs_igate_height"])
|
||||||
|
height_ft = round(height_m * FEET_PER_METER)
|
||||||
|
height = "HEIGHT=" + str(height_ft)
|
||||||
|
except:
|
||||||
|
logger.error("Cannot parse 'aprs_igate_height', expected float: " + str(pm["aprs_igate_height"]))
|
||||||
|
|
||||||
|
if((len(comment) > 0) and ((comment[0] != '"') or (comment[len(comment)-1] != '"'))):
|
||||||
|
comment = "\"" + comment + "\""
|
||||||
|
elif(len(comment) == 0):
|
||||||
|
comment = "\"\""
|
||||||
|
|
||||||
|
pbeacon = "PBEACON sendto=IG delay=0:30 every=60:00 symbol={symbol} lat={lat} long={lon} {height} {gain} {adir} comment={comment}".format(
|
||||||
|
symbol=symbol, lat=lat, lon=lon, height=height, gain=gain, adir=adir, comment=comment )
|
||||||
|
|
||||||
|
logger.info("APRS PBEACON String: " + pbeacon)
|
||||||
|
|
||||||
|
config += "\n" + pbeacon + "\n"
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
@ -71,15 +71,22 @@ class PropertyManager(ABC):
|
|||||||
pass
|
pass
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def _fireCallbacks(self, name, value):
|
def _fireCallbacks(self, changes):
|
||||||
|
if not changes:
|
||||||
|
return
|
||||||
for c in self.subscribers:
|
for c in self.subscribers:
|
||||||
try:
|
try:
|
||||||
if c.getName() is None:
|
if c.getName() is None:
|
||||||
c.call(name, value)
|
c.call(changes)
|
||||||
elif c.getName() == name:
|
except Exception:
|
||||||
c.call(value)
|
logger.exception("exception while firing changes")
|
||||||
except Exception as e:
|
for name in changes:
|
||||||
logger.exception(e)
|
for c in self.subscribers:
|
||||||
|
try:
|
||||||
|
if c.getName() == name:
|
||||||
|
c.call(changes[name])
|
||||||
|
except Exception:
|
||||||
|
logger.exception("exception while firing changes")
|
||||||
|
|
||||||
|
|
||||||
class PropertyLayer(PropertyManager):
|
class PropertyLayer(PropertyManager):
|
||||||
@ -97,7 +104,7 @@ class PropertyLayer(PropertyManager):
|
|||||||
if name in self.properties and self.properties[name] == value:
|
if name in self.properties and self.properties[name] == value:
|
||||||
return
|
return
|
||||||
self.properties[name] = value
|
self.properties[name] = value
|
||||||
self._fireCallbacks(name, value)
|
self._fireCallbacks({name: value})
|
||||||
|
|
||||||
def __dict__(self):
|
def __dict__(self):
|
||||||
return {k: v for k, v in self.properties.items()}
|
return {k: v for k, v in self.properties.items()}
|
||||||
@ -116,10 +123,9 @@ class PropertyFilter(PropertyManager):
|
|||||||
self.props = props
|
self.props = props
|
||||||
self.pm.wire(self.receiveEvent)
|
self.pm.wire(self.receiveEvent)
|
||||||
|
|
||||||
def receiveEvent(self, name, value):
|
def receiveEvent(self, changes):
|
||||||
if name not in self.props:
|
changesToForward = {name: value for name, value in changes.items() if name in self.props}
|
||||||
return
|
self._fireCallbacks(changesToForward)
|
||||||
self._fireCallbacks(name, value)
|
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
if item not in self.props:
|
if item not in self.props:
|
||||||
@ -157,7 +163,7 @@ class PropertyStack(PropertyManager):
|
|||||||
"""
|
"""
|
||||||
highest priority = 0
|
highest priority = 0
|
||||||
"""
|
"""
|
||||||
self._fireChanges(self._addLayer(priority, pm))
|
self._fireCallbacks(self._addLayer(priority, pm))
|
||||||
|
|
||||||
def _addLayer(self, priority: int, pm: PropertyManager):
|
def _addLayer(self, priority: int, pm: PropertyManager):
|
||||||
changes = {}
|
changes = {}
|
||||||
@ -165,8 +171,8 @@ class PropertyStack(PropertyManager):
|
|||||||
if key not in self or self[key] != pm[key]:
|
if key not in self or self[key] != pm[key]:
|
||||||
changes[key] = pm[key]
|
changes[key] = pm[key]
|
||||||
|
|
||||||
def eventClosure(name, value):
|
def eventClosure(changes):
|
||||||
self.receiveEvent(pm, name, value)
|
self.receiveEvent(pm, changes)
|
||||||
|
|
||||||
sub = pm.wire(eventClosure)
|
sub = pm.wire(eventClosure)
|
||||||
|
|
||||||
@ -177,7 +183,7 @@ class PropertyStack(PropertyManager):
|
|||||||
def removeLayer(self, pm: PropertyManager):
|
def removeLayer(self, pm: PropertyManager):
|
||||||
for layer in self.layers:
|
for layer in self.layers:
|
||||||
if layer["props"] == pm:
|
if layer["props"] == pm:
|
||||||
self._fireChanges(self._removeLayer(layer))
|
self._fireCallbacks(self._removeLayer(layer))
|
||||||
|
|
||||||
def _removeLayer(self, layer):
|
def _removeLayer(self, layer):
|
||||||
layer["sub"].cancel()
|
layer["sub"].cancel()
|
||||||
@ -201,16 +207,11 @@ class PropertyStack(PropertyManager):
|
|||||||
changes = {**changes, **self._addLayer(priority, pm)}
|
changes = {**changes, **self._addLayer(priority, pm)}
|
||||||
changes = {k: v for k, v in changes.items() if k not in originalState or originalState[k] != v}
|
changes = {k: v for k, v in changes.items() if k not in originalState or originalState[k] != v}
|
||||||
|
|
||||||
self._fireChanges(changes)
|
self._fireCallbacks(changes)
|
||||||
|
|
||||||
def _fireChanges(self, changes):
|
def receiveEvent(self, layer, changes):
|
||||||
for k, v in changes.items():
|
changesToForward = {name: value for name, value in changes.items() if layer == self._getTopLayer(name)}
|
||||||
self._fireCallbacks(k, v)
|
self._fireCallbacks(changesToForward)
|
||||||
|
|
||||||
def receiveEvent(self, layer, name, value):
|
|
||||||
if layer != self._getTopLayer(name):
|
|
||||||
return
|
|
||||||
self._fireCallbacks(name, value)
|
|
||||||
|
|
||||||
def _getTopLayer(self, item):
|
def _getTopLayer(self, item):
|
||||||
layers = [la["props"] for la in sorted(self.layers, key=lambda l: l["priority"])]
|
layers = [la["props"] for la in sorted(self.layers, key=lambda l: l["priority"])]
|
||||||
|
@ -111,7 +111,7 @@ class ServiceHandler(SdrSourceEventClient):
|
|||||||
for service in services:
|
for service in services:
|
||||||
service.stop()
|
service.stop()
|
||||||
|
|
||||||
def onFrequencyChange(self, key, value):
|
def onFrequencyChange(self, changes):
|
||||||
self.stopServices()
|
self.stopServices()
|
||||||
if not self.source.isAvailable():
|
if not self.source.isAvailable():
|
||||||
return
|
return
|
||||||
|
@ -246,7 +246,7 @@ class ServiceScheduler(SdrSourceEventClient):
|
|||||||
if state == SdrSource.BUSYSTATE_IDLE:
|
if state == SdrSource.BUSYSTATE_IDLE:
|
||||||
self.scheduleSelection()
|
self.scheduleSelection()
|
||||||
|
|
||||||
def onFrequencyChange(self, name, value):
|
def onFrequencyChange(self, changes):
|
||||||
self.scheduleSelection()
|
self.scheduleSelection()
|
||||||
|
|
||||||
def selectProfile(self):
|
def selectProfile(self):
|
||||||
|
@ -96,7 +96,7 @@ class SdrSource(ABC):
|
|||||||
return self.commandMapper
|
return self.commandMapper
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def onPropertyChange(self, name, value):
|
def onPropertyChange(self, changes):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def wireEvents(self):
|
def wireEvents(self):
|
||||||
|
@ -29,22 +29,22 @@ class ConnectorSource(SdrSource):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def sendControlMessage(self, prop, value):
|
def sendControlMessage(self, changes):
|
||||||
logger.debug("sending property change over control socket: {0} changed to {1}".format(prop, value))
|
for prop, value in changes.items():
|
||||||
self.controlSocket.sendall("{prop}:{value}\n".format(prop=prop, value=value).encode())
|
logger.debug("sending property change over control socket: {0} changed to {1}".format(prop, value))
|
||||||
|
self.controlSocket.sendall("{prop}:{value}\n".format(prop=prop, value=value).encode())
|
||||||
|
|
||||||
def onPropertyChange(self, prop, value):
|
def onPropertyChange(self, changes):
|
||||||
if self.monitor is None:
|
if self.monitor is None:
|
||||||
return
|
return
|
||||||
if (
|
if (
|
||||||
(prop == "center_freq" or prop == "lfo_offset")
|
("center_freq" in changes or "lfo_offset" in changes)
|
||||||
and "lfo_offset" in self.sdrProps
|
and "lfo_offset" in self.sdrProps
|
||||||
and self.sdrProps["lfo_offset"] is not None
|
and self.sdrProps["lfo_offset"] is not None
|
||||||
):
|
):
|
||||||
freq = self.sdrProps["center_freq"] + self.sdrProps["lfo_offset"]
|
changes["center_freq"] = self.sdrProps["center_freq"] + self.sdrProps["lfo_offset"]
|
||||||
self.sendControlMessage("center_freq", freq)
|
changes.pop("lfo_offset", None)
|
||||||
else:
|
self.sendControlMessage(changes)
|
||||||
self.sendControlMessage(prop, value)
|
|
||||||
|
|
||||||
def postStart(self):
|
def postStart(self):
|
||||||
logger.debug("opening control socket...")
|
logger.debug("opening control socket...")
|
||||||
|
@ -7,12 +7,8 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class DirectSource(SdrSource, metaclass=ABCMeta):
|
class DirectSource(SdrSource, metaclass=ABCMeta):
|
||||||
def onPropertyChange(self, name, value):
|
def onPropertyChange(self, changes):
|
||||||
logger.debug(
|
logger.debug("restarting sdr source due to property changes: {0}".format(changes))
|
||||||
"restarting sdr source due to property change: {0} changed to {1}".format(
|
|
||||||
name, value
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.stop()
|
self.stop()
|
||||||
self.sleepOnRestart()
|
self.sleepOnRestart()
|
||||||
self.start()
|
self.start()
|
||||||
|
@ -30,7 +30,6 @@ class FifiSdrSource(DirectSource):
|
|||||||
values = self.getCommandValues()
|
values = self.getCommandValues()
|
||||||
self.sendRockProgFrequency(values["tuner_freq"])
|
self.sendRockProgFrequency(values["tuner_freq"])
|
||||||
|
|
||||||
def onPropertyChange(self, name, value):
|
def onPropertyChange(self, changes):
|
||||||
if name != "center_freq":
|
if "center_freq" in changes:
|
||||||
return
|
self.sendRockProgFrequency(changes["center_freq"])
|
||||||
self.sendRockProgFrequency(value)
|
|
||||||
|
@ -6,8 +6,8 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class Resampler(DirectSource):
|
class Resampler(DirectSource):
|
||||||
def onPropertyChange(self, name, value):
|
def onPropertyChange(self, changes):
|
||||||
logger.warning("Resampler is unable to handle property change ({0} changed to {1})".format(name, value))
|
logger.warning("Resampler is unable to handle property changes: {0}".format(changes))
|
||||||
|
|
||||||
def __init__(self, props, sdr):
|
def __init__(self, props, sdr):
|
||||||
sdrProps = sdr.getProps()
|
sdrProps = sdr.getProps()
|
||||||
|
@ -80,9 +80,12 @@ class SoapyConnectorSource(ConnectorSource, metaclass=ABCMeta):
|
|||||||
values["soapy_settings"] = settings
|
values["soapy_settings"] = settings
|
||||||
return values
|
return values
|
||||||
|
|
||||||
def onPropertyChange(self, prop, value):
|
def onPropertyChange(self, changes):
|
||||||
mappings = self.getSoapySettingsMappings()
|
mappings = self.getSoapySettingsMappings()
|
||||||
if prop in mappings.keys():
|
settings = {}
|
||||||
value = "{0}={1}".format(mappings[prop], self.convertSoapySettingsValue(value))
|
for prop, value in changes.items():
|
||||||
prop = "settings"
|
if prop in mappings.keys():
|
||||||
super().onPropertyChange(prop, value)
|
settings[mappings[prop]] = self.convertSoapySettingsValue(value)
|
||||||
|
if settings:
|
||||||
|
changes["settings"] = ",".join("{0}={1}".format(k, v) for k, v in settings.items())
|
||||||
|
super().onPropertyChange(changes)
|
||||||
|
@ -11,7 +11,7 @@ class PropertyFilterTest(TestCase):
|
|||||||
pf = PropertyFilter(pm, "testkey")
|
pf = PropertyFilter(pm, "testkey")
|
||||||
self.assertEqual(pf["testkey"], "testvalue")
|
self.assertEqual(pf["testkey"], "testvalue")
|
||||||
|
|
||||||
def testMissesPropert(self):
|
def testMissesProperty(self):
|
||||||
pm = PropertyLayer()
|
pm = PropertyLayer()
|
||||||
pm["testkey"] = "testvalue"
|
pm["testkey"] = "testvalue"
|
||||||
pf = PropertyFilter(pm, "other_key")
|
pf = PropertyFilter(pm, "other_key")
|
||||||
@ -25,7 +25,7 @@ class PropertyFilterTest(TestCase):
|
|||||||
mock = Mock()
|
mock = Mock()
|
||||||
pf.wire(mock.method)
|
pf.wire(mock.method)
|
||||||
pm["testkey"] = "testvalue"
|
pm["testkey"] = "testvalue"
|
||||||
mock.method.assert_called_once_with("testkey", "testvalue")
|
mock.method.assert_called_once_with({"testkey": "testvalue"})
|
||||||
|
|
||||||
def testForwardsPropertyEvent(self):
|
def testForwardsPropertyEvent(self):
|
||||||
pm = PropertyLayer()
|
pm = PropertyLayer()
|
||||||
|
@ -19,7 +19,7 @@ class PropertyLayerTest(TestCase):
|
|||||||
mock = Mock()
|
mock = Mock()
|
||||||
pm.wire(mock.method)
|
pm.wire(mock.method)
|
||||||
pm["testkey"] = "after"
|
pm["testkey"] = "after"
|
||||||
mock.method.assert_called_once_with("testkey", "after")
|
mock.method.assert_called_once_with({"testkey": "after"})
|
||||||
|
|
||||||
def testUnsubscribe(self):
|
def testUnsubscribe(self):
|
||||||
pm = PropertyLayer()
|
pm = PropertyLayer()
|
||||||
@ -27,7 +27,7 @@ class PropertyLayerTest(TestCase):
|
|||||||
mock = Mock()
|
mock = Mock()
|
||||||
sub = pm.wire(mock.method)
|
sub = pm.wire(mock.method)
|
||||||
pm["testkey"] = "between"
|
pm["testkey"] = "between"
|
||||||
mock.method.assert_called_once_with("testkey", "between")
|
mock.method.assert_called_once_with({"testkey": "between"})
|
||||||
|
|
||||||
mock.reset_mock()
|
mock.reset_mock()
|
||||||
pm.unwire(sub)
|
pm.unwire(sub)
|
||||||
|
@ -49,7 +49,7 @@ class PropertyStackTest(TestCase):
|
|||||||
mock = Mock()
|
mock = Mock()
|
||||||
stack.wire(mock.method)
|
stack.wire(mock.method)
|
||||||
layer["testkey"] = "testvalue"
|
layer["testkey"] = "testvalue"
|
||||||
mock.method.assert_called_once_with("testkey", "testvalue")
|
mock.method.assert_called_once_with({"testkey": "testvalue"})
|
||||||
|
|
||||||
def testPropertyChangeEventPriority(self):
|
def testPropertyChangeEventPriority(self):
|
||||||
low_layer = PropertyLayer()
|
low_layer = PropertyLayer()
|
||||||
@ -64,7 +64,7 @@ class PropertyStackTest(TestCase):
|
|||||||
low_layer["testkey"] = "modified low value"
|
low_layer["testkey"] = "modified low value"
|
||||||
mock.method.assert_not_called()
|
mock.method.assert_not_called()
|
||||||
high_layer["testkey"] = "modified high value"
|
high_layer["testkey"] = "modified high value"
|
||||||
mock.method.assert_called_once_with("testkey", "modified high value")
|
mock.method.assert_called_once_with({"testkey": "modified high value"})
|
||||||
|
|
||||||
def testPropertyEventOnLayerAdd(self):
|
def testPropertyEventOnLayerAdd(self):
|
||||||
low_layer = PropertyLayer()
|
low_layer = PropertyLayer()
|
||||||
@ -162,7 +162,7 @@ class PropertyStackTest(TestCase):
|
|||||||
mock = Mock()
|
mock = Mock()
|
||||||
stack.wire(mock.method)
|
stack.wire(mock.method)
|
||||||
stack.removeLayer(layer)
|
stack.removeLayer(layer)
|
||||||
mock.method.assert_called_once_with("testkey", None)
|
mock.method.assert_called_once_with({"testkey": None})
|
||||||
mock.reset_mock()
|
mock.reset_mock()
|
||||||
|
|
||||||
layer["testkey"] = "after"
|
layer["testkey"] = "after"
|
||||||
|
Loading…
Reference in New Issue
Block a user