Adding all current customizations to the original forked code.

This commit is contained in:
Marat Fayzullin 2022-11-19 14:34:47 -05:00
parent e20d94e241
commit e3780f6aea
13 changed files with 255 additions and 49 deletions

View File

@ -1,15 +1,18 @@
from csdr.chain import Chain
from pycsdr.modules import AudioResampler, Convert, AdpcmEncoder, Limit
from pycsdr.modules import AudioResampler, Convert, AdpcmEncoder, Limit, NoiseFilter
from pycsdr.types import Format
class Converter(Chain):
def __init__(self, format: Format, inputRate: int, clientRate: int):
def __init__(self, format: Format, inputRate: int, clientRate: int, nrEnabled: bool, nrThreshold: int):
workers = []
if inputRate != clientRate:
# we only have an audio resampler for float ATM so if we need to resample, we need to convert
if format != Format.FLOAT:
# we only have an audio resampler and noise filter for float ATM,
# so if we need to resample or remove noise, we need to convert
if (inputRate != clientRate or nrEnabled) and format != Format.FLOAT:
workers += [Convert(format, Format.FLOAT)]
if nrEnabled:
workers += [NoiseFilter(nrThreshold)]
if inputRate != clientRate:
workers += [AudioResampler(inputRate, clientRate), Limit(), Convert(Format.FLOAT, Format.SHORT)]
elif format != Format.SHORT:
workers += [Convert(format, Format.SHORT)]
@ -17,10 +20,12 @@ class Converter(Chain):
class ClientAudioChain(Chain):
def __init__(self, format: Format, inputRate: int, clientRate: int, compression: str):
def __init__(self, format: Format, inputRate: int, clientRate: int, compression: str, nrEnabled: bool, nrThreshold: int):
self.format = format
self.inputRate = inputRate
self.clientRate = clientRate
self.nrEnabled = nrEnabled
self.nrThreshold = nrThreshold
workers = []
converter = self._buildConverter()
if not converter.empty():
@ -30,7 +35,7 @@ class ClientAudioChain(Chain):
super().__init__(workers)
def _buildConverter(self):
return Converter(self.format, self.inputRate, self.clientRate)
return Converter(self.format, self.inputRate, self.clientRate, self.nrEnabled, self.nrThreshold)
def _updateConverter(self):
converter = self._buildConverter()
@ -70,3 +75,15 @@ class ClientAudioChain(Chain):
else:
if index >= 0:
self.remove(index)
def setNrEnabled(self, nrEnabled: bool) -> None:
if nrEnabled == self.nrEnabled:
return
self.nrEnabled = nrEnabled
self._updateConverter()
def setNrThreshold(self, nrThreshold: int) -> None:
if nrThreshold == self.nrThreshold:
return
self.nrThreshold = nrThreshold
self._updateConverter()

View File

@ -24,5 +24,7 @@
<g id="play-button"><circle cx="350" cy="350" r="330" fill="none" stroke="#fff" stroke-width="36"/><path d="M195 211v278l366-139z" fill="#fff"/></g>
<g id="meta-mute" stroke="#fff" stroke-width="5"><path stroke-linejoin="round" style="paint-order:fill" fill="none" d="m21.989 47.699 17.4 15.051V13.769L22.235 28.606H6v19.093z" transform="matrix(5.3513 0 0 5.3723 -.73 -1.542)"/><path d="m48.652 50.27 20.743-24.299M69.395 50.27 48.652 25.971" stroke-linecap="round" transform="matrix(5.3513 0 0 5.3723 -.73 -1.542)"/></g>
<g id="waterfall-continuous"><g stroke="#fff" stroke-width="8"><path d="M5 40A35 35 0 0 1 26.606 7.664a35 35 0 0 1 38.143 7.587" fill="none"/><path d="m68.284 11.716 2.828 9.9-9.899-2.829z" fill="#fff"/></g><path d="m48.008 48.144 2.816 8.624h9.035L45.075 14h-9.739L20.141 56.768h8.976l2.875-8.624zm-2.405-7.333H34.456l5.573-16.72z" fill="#fff" aria-label="A"/><g stroke="#fff" stroke-width="8"><path d="M75 40a35 35 0 0 1-21.606 32.336 35 35 0 0 1-38.143-7.587" fill="none"/><path d="m11.716 68.284-2.828-9.9 9.899 2.829z" fill="#fff"/></g></g>
<g id="noise-reduce" fill="#fff" stroke-width=".767"><path d="m 36.606611,74.237282 -9.008789,0 L 11.11833,30.951149 q 0.659179,3.164062 0.659179,4.96582 l 0,38.320313 -10.0195308,0 0,-68.7744143 9.0087888,0 16.479492,42.4072263 Q 26.58708,44.706032 26.58708,42.904274 l 0,-37.4414063 10.019531,0 0,68.7744143 z"/><path d="m 80.200361,74.237282 -10.722656,0 L 58.139814,42.28904 q 0,0 0,-6.196289 l 10.107422,0 0,-21.796875 -12.963867,0 0,59.941406 -10.283203,0 0,-68.7744143 27.290039,0 q 6.196289,0 6.196289,6.3281253 l 0,25.004883 q 0,4.350585 -2.944336,5.625 -1.582031,0.659179 -6.679688,0.659179 l 11.337891,31.157227 z"/></g>
<g id="disabled" fill="#fff" stroke-width=".767"><path d="M 59.826868,-0.58229828 28.318079,81.155983 l -7.602539,0 31.376953,-81.73828128 7.734375,0 z"/></g>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -201,6 +201,12 @@
</div>
<input title="Waterfall maximum level" id="openwebrx-waterfall-color-max" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(1);" oninput="updateVolume()">
</div>
<div class="openwebrx-panel-line">
<div title="Noise reduction on/off" class="openwebrx-nr-toggle openwebrx-button openwebrx-slider-button" onclick="toggleNR();">
<svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#noise-reduce"></use></svg>
</div>
<input title="Noise reduction level" disabled id="openwebrx-panel-nr" class="openwebrx-panel-slider" type="range" min="-10" max="10" value="0" step="1" onchange="updateNR()" oninput="updateNR()">
</div>
<div class="openwebrx-panel-line">
<div class="openwebrx-button openwebrx-square-button openwebrx-zoom-button" onclick="zoomInOneStep();" title="Zoom in one step"><svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#zoom-in"></use></svg></div>
<div class="openwebrx-button openwebrx-square-button openwebrx-zoom-button" onclick="zoomOutOneStep();" title="Zoom out one step"><svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#zoom-out"></use></svg></div>

View File

@ -81,6 +81,12 @@ Envelope.prototype.draw = function(visible_range){
scale_ctx.fill();
scale_ctx.globalAlpha = 1;
scale_ctx.stroke();
scale_ctx.lineWidth = 1;
scale_ctx.textAlign = "left";
scale_ctx.fillText(this.demodulator.high_cut.toString(), to_px + env_att_w, env_h2);
scale_ctx.textAlign = "right";
scale_ctx.fillText(this.demodulator.low_cut.toString(), from_px - env_att_w, env_h2);
scale_ctx.lineWidth = 3;
}
if (typeof line !== "undefined") // out of screen?
{

View File

@ -37,6 +37,7 @@ $(function(){
var retention_time = 2 * 60 * 60 * 1000;
var strokeOpacity = 0.8;
var fillOpacity = 0.35;
var callsign_url = null;
var colorKeys = {};
var colorScale = chroma.scale(['red', 'blue', 'green']).mode('hsl');
@ -286,6 +287,9 @@ $(function(){
if ('map_position_retention_time' in config) {
retention_time = config.map_position_retention_time * 1000;
}
if ('callsign_url' in config) {
callsign_url = config['callsign_url'];
}
break;
case "update":
processUpdates(json.value);
@ -340,6 +344,32 @@ $(function(){
return infowindow;
}
var linkifyCallsign = function(callsign) {
if ((callsign_url == null) || (callsign_url == ''))
return callsign;
else
return '<a target="callsign_info" href="' +
callsign_url.replaceAll('{}', callsign.replace(new RegExp('-.*$'), '')) +
'">' + callsign + '</a>';
};
var distanceKm = function(p1, p2) {
// Earth radius in km
var R = 6371.0;
// Convert degrees to radians
var rlat1 = p1.lat() * (Math.PI/180);
var rlat2 = p2.lat() * (Math.PI/180);
// Compute difference in radians
var difflat = rlat2-rlat1;
var difflon = (p2.lng()-p1.lng()) * (Math.PI/180);
// Compute distance
d = 2 * R * Math.asin(Math.sqrt(
Math.sin(difflat/2) * Math.sin(difflat/2) +
Math.cos(rlat1) * Math.cos(rlat2) * Math.sin(difflon/2) * Math.sin(difflon/2)
));
return Math.round(d);
}
var infowindow;
var showLocatorInfoWindow = function(locator, pos) {
var infowindow = getInfoWindow();
@ -351,13 +381,15 @@ $(function(){
}).sort(function(a, b){
return b.lastseen - a.lastseen;
});
var distance = receiverMarker?
" at " + distanceKm(receiverMarker.position, pos) + " km" : "";
infowindow.setContent(
'<h3>Locator: ' + locator + '</h3>' +
'<h3>Locator: ' + locator + distance + '</h3>' +
'<div>Active Callsigns:</div>' +
'<ul>' +
inLocator.map(function(i){
var timestring = moment(i.lastseen).fromNow();
var message = i.callsign + ' (' + timestring + ' using ' + i.mode;
var message = linkifyCallsign(i.callsign) + ' (' + timestring + ' using ' + i.mode;
if (i.band) message += ' on ' + i.band;
message += ')';
return '<li>' + message + '</li>'
@ -374,11 +406,15 @@ $(function(){
var marker = markers[callsign];
var timestring = moment(marker.lastseen).fromNow();
var commentString = "";
var distance = "";
if (marker.comment) {
commentString = '<div>' + marker.comment + '</div>';
}
if (receiverMarker) {
distance = " at " + distanceKm(receiverMarker.position, marker.position) + " km";
}
infowindow.setContent(
'<h3>' + callsign + '</h3>' +
'<h3>' + linkifyCallsign(callsign) + distance + '</h3>' +
'<div>' + timestring + ' using ' + marker.mode + ( marker.band ? ' on ' + marker.band : '' ) + '</div>' +
commentString
);

View File

@ -31,6 +31,9 @@ var fft_compression = "none";
var fft_codec;
var waterfall_setup_done = 0;
var secondary_fft_size;
var tuning_step = 1;
var nr_enabled = false;
var nr_threshold = 0;
function updateVolume() {
audioEngine.setVolume(parseFloat($("#openwebrx-panel-volume").val()) / 100);
@ -51,6 +54,28 @@ function toggleMute() {
updateVolume();
}
function updateNR() {
var $nrPanel = $('#openwebrx-panel-nr');
nr_threshold = Math.round(parseFloat($nrPanel.val()));
$nrPanel.attr('title', 'Noise level (' + nr_threshold + ' dB)');
nr_changed();
}
function toggleNR() {
var $nrPanel = $('#openwebrx-panel-nr');
if ($nrPanel.prop('disabled')) {
$nrPanel.prop('disabled', false);
nr_enabled = true;
} else {
$nrPanel.prop('disabled', true);
nr_enabled = false;
}
nr_changed();
}
function zoomInOneStep() {
zoom_set(zoom_level + 1);
}
@ -257,18 +282,23 @@ var scale_canvas_drag_params = {
};
function scale_canvas_mousedown(evt) {
// Left button only
if (evt.button == 0) {
scale_canvas_drag_params.mouse_down = true;
scale_canvas_drag_params.drag = false;
scale_canvas_drag_params.start_x = evt.pageX;
scale_canvas_drag_params.key_modifiers.shiftKey = evt.shiftKey;
scale_canvas_drag_params.key_modifiers.altKey = evt.altKey;
scale_canvas_drag_params.key_modifiers.ctrlKey = evt.ctrlKey;
}
evt.preventDefault();
}
function scale_offset_freq_from_px(x, visible_range) {
if (typeof visible_range === "undefined") visible_range = get_visible_freq_range();
return (visible_range.start + visible_range.bw * (x / waterfallWidth())) - center_freq;
var f = (visible_range.start + visible_range.bw * (x / waterfallWidth())) - center_freq;
return tuning_step>0? Math.round(f / tuning_step) * tuning_step : f;
}
function scale_canvas_mousemove(evt) {
@ -307,6 +337,7 @@ function scale_canvas_end_drag(x) {
}
function scale_canvas_mouseup(evt) {
if (evt.button == 0)
scale_canvas_end_drag(evt.pageX);
}
@ -513,11 +544,16 @@ function resize_scale() {
function canvas_get_freq_offset(relativeX) {
var rel = (relativeX / canvas_container.clientWidth);
return Math.round((bandwidth * rel) - (bandwidth / 2));
var off = (bandwidth * rel) - (bandwidth / 2);
return tuning_step>0?
Math.round(off / tuning_step) * tuning_step : Math.round(off);
}
function canvas_get_frequency(relativeX) {
return center_freq + canvas_get_freq_offset(relativeX);
var f = center_freq + canvas_get_freq_offset(relativeX);
return tuning_step>0? Math.round(f / tuning_step) * tuning_step : f;
}
@ -535,16 +571,23 @@ function format_frequency(format, freq_hz, pre_divide, decimals) {
var canvas_drag = false;
var canvas_drag_min_delta = 1;
var canvas_mouse_down = false;
var canvas_mouse2_down = 0;
var canvas_drag_last_x;
var canvas_drag_last_y;
var canvas_drag_start_x;
var canvas_drag_start_y;
function canvas_mousedown(evt) {
if (evt.button > 0)
if (canvas_mouse2_down == 0)
canvas_mouse2_down = evt.button;
else {
canvas_mouse_down = true;
canvas_drag = false;
canvas_drag_last_x = canvas_drag_start_x = evt.pageX;
canvas_drag_last_y = canvas_drag_start_y = evt.pageY;
}
evt.preventDefault(); //don't show text selection mouse pointer
}
@ -581,6 +624,10 @@ function canvas_container_mouseleave() {
}
function canvas_mouseup(evt) {
if (evt.button > 0) {
if (evt.button == canvas_mouse2_down)
canvas_mouse2_down = 0;
} else {
if (!waterfall_setup_done) return;
var relativeX = get_relative_x(evt);
@ -592,6 +639,7 @@ function canvas_mouseup(evt) {
}
canvas_mouse_down = false;
}
}
function canvas_end_drag() {
canvas_container.style.cursor = "crosshair";
@ -618,7 +666,16 @@ function canvas_mousewheel(evt) {
if (!waterfall_setup_done) return;
var relativeX = get_relative_x(evt);
var dir = (evt.deltaY / Math.abs(evt.deltaY)) > 0;
// Zoom when mouse button down, tune otherwise
if (canvas_mouse2_down > 0) {
zoom_step(dir, relativeX, zoom_center_where_calc(evt.pageX));
} else {
var f = $('#openwebrx-panel-receiver').demodulatorPanel().getDemodulator().get_offset_frequency();
f += dir? -tuning_step : tuning_step;
$('#openwebrx-panel-receiver').demodulatorPanel().getDemodulator().set_offset_frequency(f);
}
evt.preventDefault();
}
@ -771,11 +828,15 @@ function on_ws_recv(evt) {
$('#openwebrx-sdr-profiles-listbox').val(currentprofile.toString());
waterfall_clear();
zoom_set(0);
}
if ('tuning_precision' in config)
$('#openwebrx-panel-receiver').demodulatorPanel().setTuningPrecision(config['tuning_precision']);
if ('tuning_step' in config)
tuning_step = config['tuning_step'];
break;
case "secondary_config":
var s = json['value'];
@ -935,9 +996,15 @@ var waterfall_measure_minmax_now = false;
var waterfall_measure_minmax_continuous = false;
function waterfall_measure_minmax_do(what) {
// Get visible range
var range = get_visible_freq_range();
var start = center_freq - bandwidth / 2;
// this is based on an oversampling factor of about 1,25
var ignored = .1 * what.length;
var data = what.slice(ignored, -ignored);
range.start = Math.max(0.1, (range.start - start) / bandwidth);
range.end = Math.min(0.9, (range.end - start) / bandwidth);
var data = what.slice(range.start * what.length, range.end * what.length);
return {
min: Math.min.apply(Math, data),
max: Math.max.apply(Math, data)
@ -1556,3 +1623,13 @@ function sdr_profile_changed() {
var value = $('#openwebrx-sdr-profiles-listbox').val();
ws.send(JSON.stringify({type: "selectprofile", params: {profile: value}}));
}
function nr_changed() {
ws.send(JSON.stringify({
"type": "connectionproperties",
"params": {
"nr_enabled": nr_enabled,
"nr_threshold": nr_threshold
}
}));
}

View File

@ -33,6 +33,7 @@ defaultConfig = PropertyLayer(
samp_rate=2400000,
start_freq=439275000,
start_mod="nfm",
tuning_step="1000",
),
"2m": PropertyLayer(
name="2m",
@ -41,6 +42,7 @@ defaultConfig = PropertyLayer(
samp_rate=2048000,
start_freq=145725000,
start_mod="nfm",
tuning_step="1000",
),
}
),
@ -57,6 +59,7 @@ defaultConfig = PropertyLayer(
samp_rate=384000,
start_freq=14070000,
start_mod="usb",
tuning_step="500",
),
"30m": PropertyLayer(
name="30m",
@ -64,6 +67,7 @@ defaultConfig = PropertyLayer(
samp_rate=192000,
start_freq=10142000,
start_mod="usb",
tuning_step="500",
),
"40m": PropertyLayer(
name="40m",
@ -71,6 +75,7 @@ defaultConfig = PropertyLayer(
samp_rate=256000,
start_freq=7070000,
start_mod="lsb",
tuning_step="500",
),
"80m": PropertyLayer(
name="80m",
@ -78,6 +83,7 @@ defaultConfig = PropertyLayer(
samp_rate=384000,
start_freq=3570000,
start_mod="lsb",
tuning_step="500",
),
"49m": PropertyLayer(
name="49m Broadcast",
@ -85,6 +91,7 @@ defaultConfig = PropertyLayer(
samp_rate=384000,
start_freq=6070000,
start_mod="am",
tuning_step="1000",
),
}
),
@ -102,6 +109,7 @@ defaultConfig = PropertyLayer(
samp_rate=500000,
start_freq=14070000,
start_mod="usb",
tuning_step="500",
),
"30m": PropertyLayer(
name="30m",
@ -110,6 +118,7 @@ defaultConfig = PropertyLayer(
samp_rate=250000,
start_freq=10142000,
start_mod="usb",
tuning_step="500",
),
"40m": PropertyLayer(
name="40m",
@ -118,6 +127,7 @@ defaultConfig = PropertyLayer(
samp_rate=500000,
start_freq=7070000,
start_mod="lsb",
tuning_step="500",
),
"80m": PropertyLayer(
name="80m",
@ -126,6 +136,7 @@ defaultConfig = PropertyLayer(
samp_rate=500000,
start_freq=3570000,
start_mod="lsb",
tuning_step="500",
),
"49m": PropertyLayer(
name="49m Broadcast",
@ -134,6 +145,7 @@ defaultConfig = PropertyLayer(
samp_rate=500000,
start_freq=6070000,
start_mod="am",
tuning_step="1000",
),
}
),
@ -147,6 +159,7 @@ defaultConfig = PropertyLayer(
squelch_auto_margin=10,
google_maps_api_key="",
map_position_retention_time=2 * 60 * 60,
callsign_url="https://www.qrzcq.com/call/{}",
decoding_queue_workers=2,
decoding_queue_length=10,
wsjt_decoding_depth=3,

View File

@ -120,6 +120,7 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient):
"start_mod",
"start_freq",
"center_freq",
"tuning_step",
"initial_squelch_level",
"sdr_id",
"profile_id",
@ -456,6 +457,7 @@ class MapConnection(OpenWebRxClient):
"google_maps_api_key",
"receiver_gps",
"map_position_retention_time",
"callsign_url",
"receiver_name",
)
filtered_config.wire(self.write_config)

View File

@ -168,6 +168,13 @@ class GeneralSettingsController(SettingsFormController):
infotext="Specifies how log markers / grids will remain visible on the map",
append="s",
),
TextInput(
"callsign_url",
"Callsign database URL",
infotext="Specifies callsign lookup URL, such as QRZ.COM "
+ "or QRZCQ.COM. Place curly brackers ({}) where callsign "
+ "is supposed to be.",
),
),
]

View File

@ -34,10 +34,12 @@ class ClientDemodulatorSecondaryDspEventClient(ABC):
class ClientDemodulatorChain(Chain):
def __init__(self, demod: BaseDemodulatorChain, sampleRate: int, outputRate: int, hdOutputRate: int, audioCompression: str, secondaryDspEventReceiver: ClientDemodulatorSecondaryDspEventClient):
def __init__(self, demod: BaseDemodulatorChain, sampleRate: int, outputRate: int, hdOutputRate: int, audioCompression: str, nrEnabled: bool, nrThreshold: int, secondaryDspEventReceiver: ClientDemodulatorSecondaryDspEventClient):
self.sampleRate = sampleRate
self.outputRate = outputRate
self.hdOutputRate = hdOutputRate
self.nrEnabled = nrEnabled
self.nrThreshold = nrThreshold
self.secondaryDspEventReceiver = secondaryDspEventReceiver
self.selector = Selector(sampleRate, outputRate)
self.selector.setBandpass(-4000, 4000)
@ -50,7 +52,7 @@ class ClientDemodulatorChain(Chain):
self.wfmDeemphasisTau = 50e-6
inputRate = demod.getFixedAudioRate() if isinstance(demod, FixedAudioRateChain) else outputRate
oRate = hdOutputRate if isinstance(demod, HdAudio) else outputRate
self.clientAudioChain = ClientAudioChain(demod.getOutputFormat(), inputRate, oRate, audioCompression)
self.clientAudioChain = ClientAudioChain(demod.getOutputFormat(), inputRate, oRate, audioCompression, nrEnabled, nrThreshold)
self.secondaryFftSize = 2048
self.secondaryFftOverlapFactor = 0.3
self.secondaryFftFps = 9
@ -251,6 +253,12 @@ class ClientDemodulatorChain(Chain):
def setAudioCompression(self, compression: str) -> None:
self.clientAudioChain.setAudioCompression(compression)
def setNrEnabled(self, nrEnabled: bool) -> None:
self.clientAudioChain.setNrEnabled(nrEnabled)
def setNrThreshold(self, nrThreshold: int) -> None:
self.clientAudioChain.setNrThreshold(nrThreshold)
def setSquelchLevel(self, level: float) -> None:
if level == self.squelchLevel:
return
@ -409,6 +417,8 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient)
"mod": ModulationValidator(),
"secondary_offset_freq": "int",
"dmr_filter": "int",
"nr_enabled": "bool",
"nr_threshold": "int",
}
self.localProps = PropertyValidator(PropertyLayer().filter(*validators.keys()), validators)
@ -436,6 +446,8 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient)
output_rate=12000,
hd_output_rate=48000,
digital_voice_codecserver="",
nr_enabled=False,
nr_threshold=0
).readonly()
)
@ -445,6 +457,8 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient)
self.props["output_rate"],
self.props["hd_output_rate"],
self.props["audio_compression"],
self.props["nr_enabled"],
self.props["nr_threshold"],
self
)
@ -487,6 +501,8 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient)
self.props.wireProperty("wfm_deemphasis_tau", self.chain.setWfmDeemphasisTau),
self.props.wireProperty("secondary_mod", self.setSecondaryDemodulator),
self.props.wireProperty("secondary_offset_freq", self.chain.setSecondaryFrequencyOffset),
self.props.wireProperty("nr_enabled", self.chain.setNrEnabled),
self.props.wireProperty("nr_threshold", self.chain.setNrThreshold),
]
# wire power level output

View File

@ -94,4 +94,5 @@ validator_types = {
"int": IntegerValidator,
"number": NumberValidator,
"num": NumberValidator,
"bool": BoolValidator,
}

View File

@ -12,8 +12,8 @@ from owrx.command import CommandMapper
from owrx.socket import getAvailablePort
from owrx.property import PropertyStack, PropertyLayer, PropertyFilter, PropertyCarousel, PropertyDeleted
from owrx.property.filter import ByLambda
from owrx.form.input import Input, TextInput, NumberInput, CheckboxInput, ModesInput, ExponentialInput
from owrx.form.input.converter import OptionalConverter
from owrx.form.input import Input, TextInput, NumberInput, CheckboxInput, ModesInput, ExponentialInput, DropdownInput, Option
from owrx.form.input.converter import OptionalConverter, IntConverter
from owrx.form.input.device import GainInput, SchedulerInput, WaterfallLevelsInput
from owrx.form.input.validator import RequiredValidator
from owrx.form.section import OptionalSection
@ -277,8 +277,9 @@ class SdrSource(ABC):
if self.monitor:
return
if self.isFailed():
return
# @@@
# if self.isFailed():
# return
try:
self.preStart()
@ -568,6 +569,12 @@ class SdrDeviceDescription(object):
ExponentialInput("samp_rate", "Sample rate", "S/s"),
ExponentialInput("start_freq", "Initial frequency", "Hz"),
ModesInput("start_mod", "Initial modulation"),
DropdownInput(
"tuning_step",
"Tuning step",
options=[Option(str(i), "{} Hz".format(i)) for i in [1, 100, 500, 1000, 2500, 3000, 5000, 6000, 10000, 12000, 50000]],
converter=IntConverter(),
),
NumberInput("initial_squelch_level", "Initial squelch level", append="dBFS"),
]
@ -592,7 +599,7 @@ class SdrDeviceDescription(object):
return keys
def getProfileMandatoryKeys(self):
return ["name", "center_freq", "samp_rate", "start_freq", "start_mod"]
return ["name", "center_freq", "samp_rate", "start_freq", "start_mod", "tuning_step"]
def getProfileOptionalKeys(self):
return ["initial_squelch_level", "rf_gain", "lfo_offset", "waterfall_levels"]

View File

@ -1,6 +1,7 @@
from owrx.source.soapy import SoapyConnectorSource, SoapyConnectorDeviceDescription
from owrx.form.input import Input, CheckboxInput, DropdownInput, DropdownEnum
from owrx.form.input.device import BiasTeeInput
from owrx.form.input import Input, CheckboxInput, DropdownInput, NumberInput, DropdownEnum
from owrx.form.input.device import BiasTeeInput, GainInput
from owrx.form.input.validator import RangeValidator
from typing import List
@ -14,6 +15,8 @@ class SdrplaySource(SoapyConnectorSource):
"dab_notch": "dabnotch_ctrl",
"if_mode": "if_mode",
"external_reference": "extref_ctrl",
"rfgain_sel": "rfgain_sel",
"agc_setpoint": "agc_setpoint",
}
)
return mappings
@ -36,9 +39,6 @@ class SdrplayDeviceDescription(SoapyConnectorDeviceDescription):
def getName(self):
return "SDRPlay device (RSP1, RSP2, RSPDuo, RSPDx)"
def getGainStages(self):
return ["RFGR", "IFGR"]
def getInputs(self) -> List[Input]:
return super().getInputs() + [
BiasTeeInput(),
@ -55,10 +55,26 @@ class SdrplayDeviceDescription(SoapyConnectorDeviceDescription):
"IF Mode",
IfModeOptions,
),
NumberInput(
"rfgain_sel",
"RF gain reduction",
validator=RangeValidator(0, 32),
),
NumberInput(
"agc_setpoint",
"AGC setpoint",
append="dBFS",
validator=RangeValidator(-60, 0),
),
GainInput(
"rf_gain",
"IF gain reduction",
has_agc=self.hasAgc(),
),
]
def getDeviceOptionalKeys(self):
return super().getDeviceOptionalKeys() + ["bias_tee", "rf_notch", "dab_notch", "if_mode"]
return super().getDeviceOptionalKeys() + ["bias_tee", "rf_notch", "dab_notch", "if_mode", "rfgain_sel", "agc_setpoint"]
def getProfileOptionalKeys(self):
return super().getProfileOptionalKeys() + ["bias_tee", "rf_notch", "dab_notch", "if_mode"]
return super().getProfileOptionalKeys() + ["bias_tee", "rf_notch", "dab_notch", "if_mode", "rfgain_sel", "agc_setpoint"]