diff --git a/owrx/sdr.py b/owrx/sdr.py index c326301..9def8e3 100644 --- a/owrx/sdr.py +++ b/owrx/sdr.py @@ -1,49 +1,94 @@ from owrx.config import Config -from owrx.property import PropertyLayer +from owrx.property import PropertyManager, PropertyDeleted, PropertyDelegator, PropertyLayer from owrx.feature import FeatureDetector, UnknownFeatureException from owrx.source import SdrSourceState +from functools import partial import logging logger = logging.getLogger(__name__) -class SdrService(object): - sdrProps = None - sources = {} - lastPort = None +class MappedSdrSources(PropertyDelegator): + def __init__(self, pm: PropertyManager): + self.subscriptions = {} + super().__init__(PropertyLayer()) + for key, value in pm.items(): + self._addSource(key, value) + pm.wire(self.handleSdrDeviceChange) - @staticmethod - def _loadProps(): - if SdrService.sdrProps is None: - pm = Config.get() - featureDetector = FeatureDetector() + def handleSdrDeviceChange(self, changes): + for key, value in changes.items(): + if value is PropertyDeleted: + del self[key] + else: + self._addSource(key, value) - def sdrTypeAvailable(value): - try: - if not featureDetector.is_available(value["type"]): - logger.error( - 'The SDR source type "{0}" is not available. please check requirements.'.format( - value["type"] - ) - ) - return False - return True - except UnknownFeatureException: - logger.error( - 'The SDR source type "{0}" is invalid. Please check your configuration'.format(value["type"]) + def handleDeviceUpdate(self, key, value, changes): + if self.isDeviceValid(value) and key not in self: + self._addSource(key, value) + elif not self.isDeviceValid(value) and key in self: + self._removeSource(key) + + def _addSource(self, key, value): + if self.isDeviceValid(value): + self[key] = self.buildNewSource(key, value) + updateMethod = partial(self.handleDeviceUpdate, key, value) + self.subscriptions[key] = [ + value.filter("type", "profiles").wire(updateMethod), + value["profiles"].wire(updateMethod) + ] + + def _removeSource(self, key): + if key in self: + self[key].shutdown() + for sub in self.subscriptions[key]: + sub.cancel() + del self.subscriptions[key] + + def isDeviceValid(self, device): + return self._hasProfiles(device) and self._sdrTypeAvailable(device) + + def _hasProfiles(self, device): + return "profiles" in device and device["profiles"] and len(device["profiles"]) > 0 + + def _sdrTypeAvailable(self, value): + featureDetector = FeatureDetector() + try: + if not featureDetector.is_available(value["type"]): + logger.error( + 'The SDR source type "{0}" is not available. please check requirements.'.format( + value["type"] ) - return False - - # transform all dictionary items into PropertyManager object, filtering out unavailable ones - SdrService.sdrProps = { - name: value for (name, value) in pm["sdrs"].items() if sdrTypeAvailable(value) - } - logger.info( - "SDR sources loaded. Available SDRs: {0}".format( - ", ".join(x["name"] for x in SdrService.sdrProps.values()) ) + return False + return True + except UnknownFeatureException: + logger.error( + 'The SDR source type "{0}" is invalid. Please check your configuration'.format(value["type"]) ) + return False + + def buildNewSource(self, id, props): + sdrType = props["type"] + className = "".join(x for x in sdrType.title() if x.isalnum()) + "Source" + module = __import__("owrx.source.{0}".format(sdrType), fromlist=[className]) + cls = getattr(module, className) + return cls(id, props) + + def __setitem__(self, key, value): + if key in self: + self._removeSource(key) + super().__setitem__(key, value) + + def __delitem__(self, key): + if key in self: + self._removeSource(key) + super().__delitem__(key) + + +class SdrService(object): + sources = None @staticmethod def getFirstSource(): @@ -58,21 +103,14 @@ class SdrService(object): sources = SdrService.getSources() if not sources: return None - if not id in sources: + if id not in sources: return None return sources[id] @staticmethod def getSources(): - SdrService._loadProps() - for id in SdrService.sdrProps.keys(): - if id not in SdrService.sources: - props = SdrService.sdrProps[id] - sdrType = props["type"] - className = "".join(x for x in sdrType.title() if x.isalnum()) + "Source" - module = __import__("owrx.source.{0}".format(sdrType), fromlist=[className]) - cls = getattr(module, className) - SdrService.sources[id] = cls(id, props) + if SdrService.sources is None: + SdrService.sources = MappedSdrSources(Config.get()["sdrs"]) return { key: s for key, s in SdrService.sources.items()