rewrite property engine
Property class is gone; logic is now done with Layers, Stack and Filter
This commit is contained in:
parent
7562dc8ecb
commit
c83d8580ba
@ -1,4 +1,4 @@
|
|||||||
from owrx.property import PropertyManager
|
from owrx.property import PropertyLayer
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
@ -24,7 +24,7 @@ class Config:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _loadConfig():
|
def _loadConfig():
|
||||||
pm = PropertyManager()
|
pm = PropertyLayer()
|
||||||
for file in ["/etc/openwebrx/config_webrx.py", "./config_webrx.py"]:
|
for file in ["/etc/openwebrx/config_webrx.py", "./config_webrx.py"]:
|
||||||
try:
|
try:
|
||||||
spec = importlib.util.spec_from_file_location("config_webrx", file)
|
spec = importlib.util.spec_from_file_location("config_webrx", file)
|
||||||
@ -55,7 +55,7 @@ class Config:
|
|||||||
return [e for e in errors if e is not None]
|
return [e for e in errors if e is not None]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def checkTempDirectory(pm: PropertyManager):
|
def checkTempDirectory(pm: PropertyLayer):
|
||||||
key = "temporary_directory"
|
key = "temporary_directory"
|
||||||
if key not in pm or pm[key] is None:
|
if key not in pm or pm[key] is None:
|
||||||
return ConfigError(key, "temporary directory is not set")
|
return ConfigError(key, "temporary directory is not set")
|
||||||
|
@ -10,6 +10,7 @@ from owrx.bands import Bandplan
|
|||||||
from owrx.bookmarks import Bookmarks
|
from owrx.bookmarks import Bookmarks
|
||||||
from owrx.map import Map
|
from owrx.map import Map
|
||||||
from owrx.locator import Locator
|
from owrx.locator import Locator
|
||||||
|
from owrx.property import PropertyStack
|
||||||
from multiprocessing import Queue
|
from multiprocessing import Queue
|
||||||
from queue import Full
|
from queue import Full
|
||||||
import json
|
import json
|
||||||
@ -195,14 +196,14 @@ class OpenWebRxReceiverClient(Client):
|
|||||||
# send initial config
|
# send initial config
|
||||||
self.setDspProperties(self.connectionProperties)
|
self.setDspProperties(self.connectionProperties)
|
||||||
|
|
||||||
configProps = (
|
stack = PropertyStack()
|
||||||
self.sdr.getProps()
|
stack.addLayer(0, self.sdr.getProps())
|
||||||
.collect(*OpenWebRxReceiverClient.config_keys)
|
stack.addLayer(1, Config.get())
|
||||||
.defaults(Config.get())
|
configProps = stack.collect(*OpenWebRxReceiverClient.config_keys)
|
||||||
)
|
|
||||||
|
|
||||||
def sendConfig(key, value):
|
def sendConfig(key, value):
|
||||||
config = dict((key, configProps[key]) for key in OpenWebRxReceiverClient.config_keys)
|
#config = dict((key, configProps[key]) for key in OpenWebRxReceiverClient.config_keys)
|
||||||
|
config = configProps.__dict__()
|
||||||
# TODO mathematical properties? hmmmm
|
# TODO mathematical properties? hmmmm
|
||||||
config["start_offset_freq"] = configProps["start_freq"] - configProps["center_freq"]
|
config["start_offset_freq"] = configProps["start_freq"] - configProps["center_freq"]
|
||||||
# TODO this is a hack to support multiple sdrs
|
# TODO this is a hack to support multiple sdrs
|
||||||
@ -253,11 +254,10 @@ class OpenWebRxReceiverClient(Client):
|
|||||||
if not keys:
|
if not keys:
|
||||||
return
|
return
|
||||||
# only the keys in the protected property manager can be overridden from the web
|
# only the keys in the protected property manager can be overridden from the web
|
||||||
protected = (
|
stack = PropertyStack()
|
||||||
self.sdr.getProps()
|
stack.addLayer(0, self.sdr.getProps())
|
||||||
.collect(*keys)
|
stack.addLayer(1, config)
|
||||||
.defaults(config)
|
protected = stack.collect(*keys)
|
||||||
)
|
|
||||||
for key, value in params.items():
|
for key, value in params.items():
|
||||||
protected[key] = value
|
protected[key] = value
|
||||||
|
|
||||||
|
74
owrx/dsp.py
74
owrx/dsp.py
@ -4,6 +4,7 @@ from owrx.wsjt import WsjtParser
|
|||||||
from owrx.aprs import AprsParser
|
from owrx.aprs import AprsParser
|
||||||
from owrx.pocsag import PocsagParser
|
from owrx.pocsag import PocsagParser
|
||||||
from owrx.source import SdrSource
|
from owrx.source import SdrSource
|
||||||
|
from owrx.property import PropertyStack
|
||||||
from csdr import csdr
|
from csdr import csdr
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
@ -23,23 +24,30 @@ class DspManager(csdr.output):
|
|||||||
"pocsag_demod": PocsagParser(self.handler),
|
"pocsag_demod": PocsagParser(self.handler),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.localProps = (
|
stack = PropertyStack()
|
||||||
self.sdrSource.getProps()
|
stack.addLayer(0, self.sdrSource.getProps())
|
||||||
.collect(
|
stack.addLayer(1, Config.get())
|
||||||
"audio_compression",
|
self.localProps = stack.collect(
|
||||||
"fft_compression",
|
"audio_compression",
|
||||||
"digimodes_fft_size",
|
"fft_compression",
|
||||||
"csdr_dynamic_bufsize",
|
"digimodes_fft_size",
|
||||||
"csdr_print_bufsizes",
|
"csdr_dynamic_bufsize",
|
||||||
"csdr_through",
|
"csdr_print_bufsizes",
|
||||||
"digimodes_enable",
|
"csdr_through",
|
||||||
"samp_rate",
|
"digimodes_enable",
|
||||||
"digital_voice_unvoiced_quality",
|
"samp_rate",
|
||||||
"dmr_filter",
|
"digital_voice_unvoiced_quality",
|
||||||
"temporary_directory",
|
"dmr_filter",
|
||||||
"center_freq",
|
"temporary_directory",
|
||||||
)
|
"center_freq",
|
||||||
.defaults(Config.get())
|
|
||||||
|
# TODO: following properties are set from the client
|
||||||
|
"output_rate",
|
||||||
|
"squelch_level",
|
||||||
|
"secondary_mod",
|
||||||
|
"low_cut",
|
||||||
|
"high_cut",
|
||||||
|
"offset_freq",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.dsp = csdr.dsp(self)
|
self.dsp = csdr.dsp(self)
|
||||||
@ -61,19 +69,19 @@ class DspManager(csdr.output):
|
|||||||
parser.setDialFrequency(freq)
|
parser.setDialFrequency(freq)
|
||||||
|
|
||||||
self.subscriptions = [
|
self.subscriptions = [
|
||||||
self.localProps.getProperty("audio_compression").wire(self.dsp.set_audio_compression),
|
self.localProps.wireProperty("audio_compression", self.dsp.set_audio_compression),
|
||||||
self.localProps.getProperty("fft_compression").wire(self.dsp.set_fft_compression),
|
self.localProps.wireProperty("fft_compression", self.dsp.set_fft_compression),
|
||||||
self.localProps.getProperty("digimodes_fft_size").wire(self.dsp.set_secondary_fft_size),
|
self.localProps.wireProperty("digimodes_fft_size", self.dsp.set_secondary_fft_size),
|
||||||
self.localProps.getProperty("samp_rate").wire(self.dsp.set_samp_rate),
|
self.localProps.wireProperty("samp_rate", self.dsp.set_samp_rate),
|
||||||
self.localProps.getProperty("output_rate").wire(self.dsp.set_output_rate),
|
self.localProps.wireProperty("output_rate", self.dsp.set_output_rate),
|
||||||
self.localProps.getProperty("offset_freq").wire(self.dsp.set_offset_freq),
|
self.localProps.wireProperty("offset_freq", self.dsp.set_offset_freq),
|
||||||
self.localProps.getProperty("squelch_level").wire(self.dsp.set_squelch_level),
|
self.localProps.wireProperty("squelch_level", self.dsp.set_squelch_level),
|
||||||
self.localProps.getProperty("low_cut").wire(set_low_cut),
|
self.localProps.wireProperty("low_cut", set_low_cut),
|
||||||
self.localProps.getProperty("high_cut").wire(set_high_cut),
|
self.localProps.wireProperty("high_cut", set_high_cut),
|
||||||
self.localProps.getProperty("mod").wire(self.dsp.set_demodulator),
|
self.localProps.wireProperty("mod", self.dsp.set_demodulator),
|
||||||
self.localProps.getProperty("digital_voice_unvoiced_quality").wire(self.dsp.set_unvoiced_quality),
|
self.localProps.wireProperty("digital_voice_unvoiced_quality", self.dsp.set_unvoiced_quality),
|
||||||
self.localProps.getProperty("dmr_filter").wire(self.dsp.set_dmr_filter),
|
self.localProps.wireProperty("dmr_filter", self.dsp.set_dmr_filter),
|
||||||
self.localProps.getProperty("temporary_directory").wire(self.dsp.set_temporary_directory),
|
self.localProps.wireProperty("temporary_directory", self.dsp.set_temporary_directory),
|
||||||
self.localProps.collect("center_freq", "offset_freq").wire(set_dial_freq),
|
self.localProps.collect("center_freq", "offset_freq").wire(set_dial_freq),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -99,8 +107,8 @@ class DspManager(csdr.output):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.subscriptions += [
|
self.subscriptions += [
|
||||||
self.localProps.getProperty("secondary_mod").wire(set_secondary_mod),
|
self.localProps.collect("secondary_mod").wire(set_secondary_mod),
|
||||||
self.localProps.getProperty("secondary_offset_freq").wire(self.dsp.set_secondary_offset_freq),
|
self.localProps.collect("secondary_offset_freq").wire(self.dsp.set_secondary_offset_freq),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.sdrSource.addClient(self)
|
self.sdrSource.addClient(self)
|
||||||
@ -134,7 +142,7 @@ class DspManager(csdr.output):
|
|||||||
self.subscriptions = []
|
self.subscriptions = []
|
||||||
|
|
||||||
def setProperty(self, prop, value):
|
def setProperty(self, prop, value):
|
||||||
self.localProps.getProperty(prop).setValue(value)
|
self.localProps[prop] = value
|
||||||
|
|
||||||
def getClientClass(self):
|
def getClientClass(self):
|
||||||
return SdrSource.CLIENT_USER
|
return SdrSource.CLIENT_USER
|
||||||
|
18
owrx/fft.py
18
owrx/fft.py
@ -2,6 +2,7 @@ from owrx.config import Config
|
|||||||
from csdr import csdr
|
from csdr import csdr
|
||||||
import threading
|
import threading
|
||||||
from owrx.source import SdrSource
|
from owrx.source import SdrSource
|
||||||
|
from owrx.property import PropertyStack
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -13,7 +14,10 @@ class SpectrumThread(csdr.output):
|
|||||||
self.sdrSource = sdrSource
|
self.sdrSource = sdrSource
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.props = props = self.sdrSource.props.collect(
|
stack = PropertyStack()
|
||||||
|
stack.addLayer(0, self.sdrSource.props)
|
||||||
|
stack.addLayer(1, Config.get())
|
||||||
|
self.props = props = stack.collect(
|
||||||
"samp_rate",
|
"samp_rate",
|
||||||
"fft_size",
|
"fft_size",
|
||||||
"fft_fps",
|
"fft_fps",
|
||||||
@ -23,7 +27,7 @@ class SpectrumThread(csdr.output):
|
|||||||
"csdr_print_bufsizes",
|
"csdr_print_bufsizes",
|
||||||
"csdr_through",
|
"csdr_through",
|
||||||
"temporary_directory",
|
"temporary_directory",
|
||||||
).defaults(Config.get())
|
)
|
||||||
|
|
||||||
self.dsp = dsp = csdr.dsp(self)
|
self.dsp = dsp = csdr.dsp(self)
|
||||||
dsp.nc_port = self.sdrSource.getPort()
|
dsp.nc_port = self.sdrSource.getPort()
|
||||||
@ -42,11 +46,11 @@ class SpectrumThread(csdr.output):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.subscriptions = [
|
self.subscriptions = [
|
||||||
props.getProperty("samp_rate").wire(dsp.set_samp_rate),
|
props.wireProperty("samp_rate", dsp.set_samp_rate),
|
||||||
props.getProperty("fft_size").wire(dsp.set_fft_size),
|
props.wireProperty("fft_size", dsp.set_fft_size),
|
||||||
props.getProperty("fft_fps").wire(dsp.set_fft_fps),
|
props.wireProperty("fft_fps", dsp.set_fft_fps),
|
||||||
props.getProperty("fft_compression").wire(dsp.set_fft_compression),
|
props.wireProperty("fft_compression", dsp.set_fft_compression),
|
||||||
props.getProperty("temporary_directory").wire(dsp.set_temporary_directory),
|
props.wireProperty("temporary_directory", dsp.set_temporary_directory),
|
||||||
props.collect("samp_rate", "fft_size", "fft_fps", "fft_voverlap_factor").wire(set_fft_averages),
|
props.collect("samp_rate", "fft_size", "fft_fps", "fft_voverlap_factor").wire(set_fft_averages),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Subscription(object):
|
class Subscription(object):
|
||||||
def __init__(self, subscriptee, subscriber):
|
def __init__(self, subscriptee, name, subscriber):
|
||||||
self.subscriptee = subscriptee
|
self.subscriptee = subscriptee
|
||||||
|
self.name = name
|
||||||
self.subscriber = subscriber
|
self.subscriber = subscriber
|
||||||
|
|
||||||
|
def getName(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
def call(self, *args, **kwargs):
|
def call(self, *args, **kwargs):
|
||||||
self.subscriber(*args, **kwargs)
|
self.subscriber(*args, **kwargs)
|
||||||
|
|
||||||
@ -15,30 +20,39 @@ class Subscription(object):
|
|||||||
self.subscriptee.unwire(self)
|
self.subscriptee.unwire(self)
|
||||||
|
|
||||||
|
|
||||||
class Property(object):
|
class PropertyManager(ABC):
|
||||||
def __init__(self, value=None):
|
def __init__(self):
|
||||||
self.value = value
|
|
||||||
self.subscribers = []
|
self.subscribers = []
|
||||||
|
|
||||||
def getValue(self):
|
@abstractmethod
|
||||||
return self.value
|
def __getitem__(self, item):
|
||||||
|
pass
|
||||||
|
|
||||||
def setValue(self, value):
|
@abstractmethod
|
||||||
if self.value == value:
|
def __setitem__(self, key, value):
|
||||||
return self
|
pass
|
||||||
self.value = value
|
|
||||||
for c in self.subscribers:
|
@abstractmethod
|
||||||
try:
|
def __contains__(self, item):
|
||||||
c.call(self.value)
|
pass
|
||||||
except Exception as e:
|
|
||||||
logger.exception(e)
|
@abstractmethod
|
||||||
return self
|
def __dict__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def collect(self, *props):
|
||||||
|
return PropertyFilter(self, *props)
|
||||||
|
|
||||||
def wire(self, callback):
|
def wire(self, callback):
|
||||||
sub = Subscription(self, callback)
|
sub = Subscription(self, None, callback)
|
||||||
self.subscribers.append(sub)
|
self.subscribers.append(sub)
|
||||||
if self.value is not None:
|
return sub
|
||||||
sub.call(self.value)
|
|
||||||
|
def wireProperty(self, name, callback):
|
||||||
|
sub = Subscription(self, name, callback)
|
||||||
|
self.subscribers.append(sub)
|
||||||
|
if name in self:
|
||||||
|
sub.call(self[name])
|
||||||
return sub
|
return sub
|
||||||
|
|
||||||
def unwire(self, sub):
|
def unwire(self, sub):
|
||||||
@ -49,80 +63,79 @@ class Property(object):
|
|||||||
pass
|
pass
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def _fireCallbacks(self, name, value):
|
||||||
|
for c in self.subscribers:
|
||||||
|
try:
|
||||||
|
if c.getName() is None:
|
||||||
|
c.call(name, value)
|
||||||
|
elif c.getName() == name:
|
||||||
|
c.call(value)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(e)
|
||||||
|
|
||||||
class PropertyManager(object):
|
|
||||||
def collect(self, *props):
|
|
||||||
return PropertyManager(
|
|
||||||
{name: self.getProperty(name) if self.hasProperty(name) else Property() for name in props}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
class PropertyLayer(PropertyManager):
|
||||||
def __init__(self, properties=None):
|
def __init__(self, properties=None):
|
||||||
|
super().__init__()
|
||||||
self.properties = {}
|
self.properties = {}
|
||||||
self.subscribers = []
|
|
||||||
if properties is not None:
|
if properties is not None:
|
||||||
for (name, prop) in properties.items():
|
for (name, prop) in properties.items():
|
||||||
self.add(name, prop)
|
self._add(name, prop)
|
||||||
|
|
||||||
def add(self, name, prop):
|
def _add(self, name, prop):
|
||||||
self.properties[name] = prop
|
self.properties[name] = prop
|
||||||
|
self._fireCallbacks(name, prop.getValue())
|
||||||
def fireCallbacks(value):
|
|
||||||
for c in self.subscribers:
|
|
||||||
try:
|
|
||||||
c.call(name, value)
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(e)
|
|
||||||
|
|
||||||
prop.wire(fireCallbacks)
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __contains__(self, name):
|
def __contains__(self, name):
|
||||||
return self.hasProperty(name)
|
|
||||||
|
|
||||||
def __getitem__(self, name):
|
|
||||||
return self.getPropertyValue(name)
|
|
||||||
|
|
||||||
def __setitem__(self, name, value):
|
|
||||||
if not self.hasProperty(name):
|
|
||||||
self.add(name, Property())
|
|
||||||
self.getProperty(name).setValue(value)
|
|
||||||
|
|
||||||
def __dict__(self):
|
|
||||||
return {k: v.getValue() for k, v in self.properties.items()}
|
|
||||||
|
|
||||||
def hasProperty(self, name):
|
|
||||||
return name in self.properties
|
return name in self.properties
|
||||||
|
|
||||||
def getProperty(self, name):
|
def __getitem__(self, name):
|
||||||
if not self.hasProperty(name):
|
|
||||||
self.add(name, Property())
|
|
||||||
return self.properties[name]
|
return self.properties[name]
|
||||||
|
|
||||||
def getPropertyValue(self, name):
|
def __setitem__(self, name, value):
|
||||||
return self.getProperty(name).getValue()
|
logger.debug("property change: %s => %s", name, value)
|
||||||
|
self.properties[name] = value
|
||||||
|
self._fireCallbacks(name, value)
|
||||||
|
|
||||||
def wire(self, callback):
|
def __dict__(self):
|
||||||
sub = Subscription(self, callback)
|
return {k: v for k, v in self.properties.items()}
|
||||||
self.subscribers.append(sub)
|
|
||||||
return sub
|
|
||||||
|
|
||||||
def unwire(self, sub):
|
|
||||||
try:
|
|
||||||
self.subscribers.remove(sub)
|
|
||||||
except ValueError:
|
|
||||||
# happens when already removed before
|
|
||||||
pass
|
|
||||||
return self
|
|
||||||
|
|
||||||
def defaults(self, other_pm):
|
|
||||||
for (key, p) in self.properties.items():
|
|
||||||
if p.getValue() is None:
|
|
||||||
p.setValue(other_pm[key])
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
class PropertyLayers(object):
|
class PropertyFilter(PropertyManager):
|
||||||
|
def __init__(self, pm: PropertyManager, *props: str):
|
||||||
|
super().__init__()
|
||||||
|
self.pm = pm
|
||||||
|
self.props = props
|
||||||
|
self.pm.wire(self.receiveEvent)
|
||||||
|
|
||||||
|
def receiveEvent(self, name, value):
|
||||||
|
if name not in self.props:
|
||||||
|
return
|
||||||
|
self._fireCallbacks(name, value)
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
if item not in self.props:
|
||||||
|
raise KeyError(item)
|
||||||
|
return self.pm.__getitem__(item)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if key not in self.props:
|
||||||
|
raise KeyError(key)
|
||||||
|
return self.pm.__setitem__(key, value)
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
if item not in self.props:
|
||||||
|
return False
|
||||||
|
return self.pm.__contains__(item)
|
||||||
|
|
||||||
|
def __dict__(self):
|
||||||
|
return {k: v for k, v in self.pm.__dict__().items() if k in self.props}
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyStack(PropertyManager):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
self.layers = []
|
self.layers = []
|
||||||
|
|
||||||
def addLayer(self, priority: int, pm: PropertyManager):
|
def addLayer(self, priority: int, pm: PropertyManager):
|
||||||
@ -136,8 +149,26 @@ class PropertyLayers(object):
|
|||||||
if layer["props"] == pm:
|
if layer["props"] == pm:
|
||||||
self.layers.remove(layer)
|
self.layers.remove(layer)
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def _getLayer(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"])]
|
||||||
for m in layers:
|
for m in layers:
|
||||||
if item in m:
|
if item in m:
|
||||||
return m[item]
|
return m
|
||||||
|
# return top layer by default
|
||||||
|
return layers[0]
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
layer = self._getLayer(item)
|
||||||
|
return layer.__getitem__(item)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
layer = self._getLayer(key)
|
||||||
|
return layer.__setitem__(key, value)
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
layer = self._getLayer(item)
|
||||||
|
return layer.__contains__(item)
|
||||||
|
|
||||||
|
def __dict__(self):
|
||||||
|
keys = [key for l in self.layers for key in l["props"].__dict__().keys()]
|
||||||
|
return {k: self.__getitem__(k) for k in keys}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from owrx.config import Config
|
from owrx.config import Config
|
||||||
from owrx.property import PropertyManager
|
from owrx.property import PropertyLayer
|
||||||
from owrx.feature import FeatureDetector, UnknownFeatureException
|
from owrx.feature import FeatureDetector, UnknownFeatureException
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@ -19,7 +19,7 @@ class SdrService(object):
|
|||||||
featureDetector = FeatureDetector()
|
featureDetector = FeatureDetector()
|
||||||
|
|
||||||
def loadIntoPropertyManager(dict: dict):
|
def loadIntoPropertyManager(dict: dict):
|
||||||
propertyManager = PropertyManager()
|
propertyManager = PropertyLayer()
|
||||||
for (name, value) in dict.items():
|
for (name, value) in dict.items():
|
||||||
propertyManager[name] = value
|
propertyManager[name] = value
|
||||||
return propertyManager
|
return propertyManager
|
||||||
|
@ -8,7 +8,7 @@ from owrx.aprs import AprsParser
|
|||||||
from owrx.config import Config
|
from owrx.config import Config
|
||||||
from owrx.source.resampler import Resampler
|
from owrx.source.resampler import Resampler
|
||||||
from owrx.feature import FeatureDetector
|
from owrx.feature import FeatureDetector
|
||||||
from owrx.property import PropertyManager
|
from owrx.property import PropertyLayer
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
from .schedule import ServiceScheduler
|
from .schedule import ServiceScheduler
|
||||||
|
|
||||||
@ -164,7 +164,7 @@ class ServiceHandler(object):
|
|||||||
cf = (min + max) / 2
|
cf = (min + max) / 2
|
||||||
bw = max - min
|
bw = max - min
|
||||||
logger.debug("group center frequency: {0}, bandwidth: {1}".format(cf, bw))
|
logger.debug("group center frequency: {0}, bandwidth: {1}".format(cf, bw))
|
||||||
resampler_props = PropertyManager()
|
resampler_props = PropertyLayer()
|
||||||
resampler_props["center_freq"] = cf
|
resampler_props["center_freq"] = cf
|
||||||
# TODO the + 24000 is a temporary fix since the resampling optimizer does not account for required bandwidths
|
# TODO the + 24000 is a temporary fix since the resampling optimizer does not account for required bandwidths
|
||||||
resampler_props["samp_rate"] = bw + 24000
|
resampler_props["samp_rate"] = bw + 24000
|
||||||
|
@ -9,6 +9,7 @@ import signal
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from owrx.command import CommandMapper
|
from owrx.command import CommandMapper
|
||||||
from owrx.socket import getAvailablePort
|
from owrx.socket import getAvailablePort
|
||||||
|
from owrx.property import PropertyStack
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -35,7 +36,10 @@ class SdrSource(ABC):
|
|||||||
self.props = props
|
self.props = props
|
||||||
self.profile_id = None
|
self.profile_id = None
|
||||||
self.activateProfile()
|
self.activateProfile()
|
||||||
self.rtlProps = self.props.collect(*self.getEventNames()).defaults(Config.get())
|
stack = PropertyStack()
|
||||||
|
stack.addLayer(0, self.props)
|
||||||
|
stack.addLayer(1, Config.get())
|
||||||
|
self.rtlProps = stack.collect(*self.getEventNames())
|
||||||
self.wireEvents()
|
self.wireEvents()
|
||||||
self.commandMapper = None
|
self.commandMapper = None
|
||||||
|
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
import unittest
|
|
||||||
from unittest.mock import Mock
|
|
||||||
from owrx.property import Property
|
|
||||||
|
|
||||||
|
|
||||||
class PropertyTest(unittest.TestCase):
|
|
||||||
def testValue(self):
|
|
||||||
prop = Property("testvalue")
|
|
||||||
self.assertEqual(prop.getValue(), "testvalue")
|
|
||||||
|
|
||||||
def testChangeValue(self):
|
|
||||||
prop = Property("before")
|
|
||||||
prop.setValue("after")
|
|
||||||
self.assertEqual(prop.getValue(), "after")
|
|
||||||
|
|
||||||
def testInitialValueOnCallback(self):
|
|
||||||
prop = Property("before")
|
|
||||||
m = Mock()
|
|
||||||
prop.wire(m.method)
|
|
||||||
m.method.assert_called_once_with("before")
|
|
||||||
|
|
||||||
def testChangedValueOnCallback(self):
|
|
||||||
prop = Property("before")
|
|
||||||
m = Mock()
|
|
||||||
prop.wire(m.method)
|
|
||||||
m.reset_mock()
|
|
||||||
prop.setValue("after")
|
|
||||||
m.method.assert_called_with("after")
|
|
51
test/property/test_property_filter.py
Normal file
51
test/property/test_property_filter.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
from unittest.mock import Mock
|
||||||
|
from owrx.property import PropertyLayer, PropertyFilter
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyFilterTest(TestCase):
|
||||||
|
|
||||||
|
def testPassesProperty(self):
|
||||||
|
pm = PropertyLayer()
|
||||||
|
pm["testkey"] = "testvalue"
|
||||||
|
pf = PropertyFilter(pm, "testkey")
|
||||||
|
self.assertEqual(pf["testkey"], "testvalue")
|
||||||
|
|
||||||
|
def testMissesPropert(self):
|
||||||
|
pm = PropertyLayer()
|
||||||
|
pm["testkey"] = "testvalue"
|
||||||
|
pf = PropertyFilter(pm, "other_key")
|
||||||
|
self.assertFalse("testkey" in pf)
|
||||||
|
with self.assertRaises(KeyError):
|
||||||
|
x = pf["testkey"]
|
||||||
|
|
||||||
|
def testForwardsEvent(self):
|
||||||
|
pm = PropertyLayer()
|
||||||
|
pf = PropertyFilter(pm, "testkey")
|
||||||
|
mock = Mock()
|
||||||
|
pf.wire(mock.method)
|
||||||
|
pm["testkey"] = "testvalue"
|
||||||
|
mock.method.assert_called_once_with("testkey", "testvalue")
|
||||||
|
|
||||||
|
def testForwardsPropertyEvent(self):
|
||||||
|
pm = PropertyLayer()
|
||||||
|
pf = PropertyFilter(pm, "testkey")
|
||||||
|
mock = Mock()
|
||||||
|
pf.wireProperty("testkey", mock.method)
|
||||||
|
pm["testkey"] = "testvalue"
|
||||||
|
mock.method.assert_called_once_with("testvalue")
|
||||||
|
|
||||||
|
def testForwardsWrite(self):
|
||||||
|
pm = PropertyLayer()
|
||||||
|
pf = PropertyFilter(pm, "testkey")
|
||||||
|
pf["testkey"] = "testvalue"
|
||||||
|
self.assertTrue("testkey" in pm)
|
||||||
|
self.assertEqual(pm["testkey"], "testvalue")
|
||||||
|
|
||||||
|
def testOverwrite(self):
|
||||||
|
pm = PropertyLayer()
|
||||||
|
pm["testkey"] = "old value"
|
||||||
|
pf = PropertyFilter(pm, "testkey")
|
||||||
|
pf["testkey"] = "new value"
|
||||||
|
self.assertEqual(pm["testkey"], "new value")
|
||||||
|
self.assertEqual(pf["testkey"], "new value")
|
52
test/property/test_property_layer.py
Normal file
52
test/property/test_property_layer.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
from owrx.property import PropertyLayer
|
||||||
|
from unittest import TestCase
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyLayerTest(TestCase):
|
||||||
|
def testKeyIsset(self):
|
||||||
|
pm = PropertyLayer()
|
||||||
|
self.assertFalse("some_key" in pm)
|
||||||
|
|
||||||
|
def testKeyError(self):
|
||||||
|
pm = PropertyLayer()
|
||||||
|
with self.assertRaises(KeyError):
|
||||||
|
x = pm["some_key"]
|
||||||
|
|
||||||
|
def testSubscription(self):
|
||||||
|
pm = PropertyLayer()
|
||||||
|
pm["testkey"] = "before"
|
||||||
|
mock = Mock()
|
||||||
|
pm.wire(mock.method)
|
||||||
|
pm["testkey"] = "after"
|
||||||
|
mock.method.assert_called_once_with("testkey", "after")
|
||||||
|
|
||||||
|
def testUnsubscribe(self):
|
||||||
|
pm = PropertyLayer()
|
||||||
|
pm["testkey"] = "before"
|
||||||
|
mock = Mock()
|
||||||
|
sub = pm.wire(mock.method)
|
||||||
|
pm["testkey"] = "between"
|
||||||
|
mock.method.assert_called_once_with("testkey", "between")
|
||||||
|
|
||||||
|
mock.reset_mock()
|
||||||
|
pm.unwire(sub)
|
||||||
|
pm["testkey"] = "after"
|
||||||
|
mock.method.assert_not_called()
|
||||||
|
|
||||||
|
def testContains(self):
|
||||||
|
pm = PropertyLayer()
|
||||||
|
pm["testkey"] = "value"
|
||||||
|
self.assertTrue("testkey" in pm)
|
||||||
|
|
||||||
|
def testDoesNotContain(self):
|
||||||
|
pm = PropertyLayer()
|
||||||
|
self.assertFalse("testkey" in pm)
|
||||||
|
|
||||||
|
def testSubscribeBeforeSet(self):
|
||||||
|
pm = PropertyLayer()
|
||||||
|
mock = Mock()
|
||||||
|
pm.wireProperty("testkey", mock.method)
|
||||||
|
mock.method.assert_not_called()
|
||||||
|
pm["testkey"] = "newvalue"
|
||||||
|
mock.method.assert_called_once_with("newvalue")
|
@ -1,20 +1,20 @@
|
|||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
from owrx.property import PropertyManager, PropertyLayers
|
from owrx.property import PropertyLayer, PropertyStack
|
||||||
|
|
||||||
|
|
||||||
class TestPropertyLayers(TestCase):
|
class PropertyStackTest(TestCase):
|
||||||
def testLayer(self):
|
def testLayer(self):
|
||||||
om = PropertyLayers()
|
om = PropertyStack()
|
||||||
pm = PropertyManager()
|
pm = PropertyLayer()
|
||||||
pm["testkey"] = "testvalue"
|
pm["testkey"] = "testvalue"
|
||||||
om.addLayer(1, pm)
|
om.addLayer(1, pm)
|
||||||
self.assertEqual(om["testkey"], "testvalue")
|
self.assertEqual(om["testkey"], "testvalue")
|
||||||
|
|
||||||
def testHighPriority(self):
|
def testHighPriority(self):
|
||||||
om = PropertyLayers()
|
om = PropertyStack()
|
||||||
low_pm = PropertyManager()
|
low_pm = PropertyLayer()
|
||||||
high_pm = PropertyManager()
|
high_pm = PropertyLayer()
|
||||||
low_pm["testkey"] = "low value"
|
low_pm["testkey"] = "low value"
|
||||||
high_pm["testkey"] = "high value"
|
high_pm["testkey"] = "high value"
|
||||||
om.addLayer(1, low_pm)
|
om.addLayer(1, low_pm)
|
||||||
@ -22,18 +22,18 @@ class TestPropertyLayers(TestCase):
|
|||||||
self.assertEqual(om["testkey"], "high value")
|
self.assertEqual(om["testkey"], "high value")
|
||||||
|
|
||||||
def testPriorityFallback(self):
|
def testPriorityFallback(self):
|
||||||
om = PropertyLayers()
|
om = PropertyStack()
|
||||||
low_pm = PropertyManager()
|
low_pm = PropertyLayer()
|
||||||
high_pm = PropertyManager()
|
high_pm = PropertyLayer()
|
||||||
low_pm["testkey"] = "low value"
|
low_pm["testkey"] = "low value"
|
||||||
om.addLayer(1, low_pm)
|
om.addLayer(1, low_pm)
|
||||||
om.addLayer(0, high_pm)
|
om.addLayer(0, high_pm)
|
||||||
self.assertEqual(om["testkey"], "low value")
|
self.assertEqual(om["testkey"], "low value")
|
||||||
|
|
||||||
def testLayerRemoval(self):
|
def testLayerRemoval(self):
|
||||||
om = PropertyLayers()
|
om = PropertyStack()
|
||||||
low_pm = PropertyManager()
|
low_pm = PropertyLayer()
|
||||||
high_pm = PropertyManager()
|
high_pm = PropertyLayer()
|
||||||
low_pm["testkey"] = "low value"
|
low_pm["testkey"] = "low value"
|
||||||
high_pm["testkey"] = "high value"
|
high_pm["testkey"] = "high value"
|
||||||
om.addLayer(1, low_pm)
|
om.addLayer(1, low_pm)
|
Loading…
Reference in New Issue
Block a user