2020-03-21 21:40:39 +00:00
|
|
|
from owrx.config import Config
|
2021-03-20 00:10:18 +00:00
|
|
|
from owrx.property import PropertyManager, PropertyDeleted, PropertyDelegator, PropertyLayer, PropertyReadOnly
|
2019-12-21 19:58:28 +00:00
|
|
|
from owrx.feature import FeatureDetector, UnknownFeatureException
|
2021-03-20 00:10:18 +00:00
|
|
|
from owrx.source import SdrSource, SdrSourceEventClient
|
2021-03-18 17:59:38 +00:00
|
|
|
from functools import partial
|
2019-12-21 19:58:28 +00:00
|
|
|
|
|
|
|
import logging
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2021-03-18 17:59:38 +00:00
|
|
|
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)
|
2019-12-21 19:58:28 +00:00
|
|
|
|
2021-03-18 17:59:38 +00:00
|
|
|
def handleSdrDeviceChange(self, changes):
|
|
|
|
for key, value in changes.items():
|
|
|
|
if value is PropertyDeleted:
|
2021-03-20 01:16:08 +00:00
|
|
|
if key in self:
|
|
|
|
del self[key]
|
2021-03-18 17:59:38 +00:00
|
|
|
else:
|
|
|
|
self._addSource(key, value)
|
|
|
|
|
|
|
|
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:
|
2021-03-18 21:59:46 +00:00
|
|
|
del self[key]
|
2021-03-18 17:59:38 +00:00
|
|
|
|
|
|
|
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 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"]
|
2019-12-21 19:58:28 +00:00
|
|
|
)
|
|
|
|
)
|
2021-03-18 17:59:38 +00:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
except UnknownFeatureException:
|
|
|
|
logger.error(
|
|
|
|
'The SDR source type "{0}" is invalid. Please check your configuration'.format(value["type"])
|
2019-12-21 19:58:28 +00:00
|
|
|
)
|
2021-03-18 17:59:38 +00:00
|
|
|
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)
|
|
|
|
|
2021-03-18 21:59:46 +00:00
|
|
|
def _removeSource(self, key, source):
|
|
|
|
source.shutdown()
|
|
|
|
for sub in self.subscriptions[key]:
|
|
|
|
sub.cancel()
|
|
|
|
del self.subscriptions[key]
|
|
|
|
|
2021-03-18 17:59:38 +00:00
|
|
|
def __setitem__(self, key, value):
|
2021-03-18 21:59:46 +00:00
|
|
|
source = self[key] if key in self else None
|
2021-03-20 00:10:18 +00:00
|
|
|
if source is value:
|
|
|
|
return
|
2021-03-18 17:59:38 +00:00
|
|
|
super().__setitem__(key, value)
|
2021-03-18 21:59:46 +00:00
|
|
|
if source is not None:
|
|
|
|
self._removeSource(key, source)
|
2021-03-18 17:59:38 +00:00
|
|
|
|
|
|
|
def __delitem__(self, key):
|
2021-03-18 21:59:46 +00:00
|
|
|
source = self[key] if key in self else None
|
2021-03-18 17:59:38 +00:00
|
|
|
super().__delitem__(key)
|
2021-03-18 21:59:46 +00:00
|
|
|
if source is not None:
|
|
|
|
self._removeSource(key, source)
|
2021-03-18 17:59:38 +00:00
|
|
|
|
|
|
|
|
2021-03-20 00:10:18 +00:00
|
|
|
class SourceStateHandler(SdrSourceEventClient):
|
|
|
|
def __init__(self, pm, key, source: SdrSource):
|
|
|
|
self.pm = pm
|
|
|
|
self.key = key
|
|
|
|
self.source = source
|
|
|
|
|
|
|
|
def selfDestruct(self):
|
|
|
|
self.source.removeClient(self)
|
|
|
|
|
|
|
|
def onFail(self):
|
|
|
|
del self.pm[self.key]
|
|
|
|
|
|
|
|
def onDisable(self):
|
|
|
|
del self.pm[self.key]
|
|
|
|
|
|
|
|
def onEnable(self):
|
|
|
|
self.pm[self.key] = self.source
|
|
|
|
|
|
|
|
def onShutdown(self):
|
|
|
|
del self.pm[self.key]
|
|
|
|
|
|
|
|
|
|
|
|
class ActiveSdrSources(PropertyReadOnly):
|
|
|
|
def __init__(self, pm: PropertyManager):
|
|
|
|
self.handlers = {}
|
|
|
|
self._layer = PropertyLayer()
|
|
|
|
super().__init__(self._layer)
|
|
|
|
for key, value in pm.items():
|
|
|
|
self._addSource(key, value)
|
|
|
|
pm.wire(self.handleSdrDeviceChange)
|
|
|
|
|
|
|
|
def handleSdrDeviceChange(self, changes):
|
|
|
|
for key, value in changes.items():
|
|
|
|
if value is PropertyDeleted:
|
|
|
|
self._removeSource(key)
|
|
|
|
else:
|
|
|
|
self._addSource(key, value)
|
|
|
|
|
|
|
|
def isAvailable(self, source: SdrSource):
|
|
|
|
return source.isEnabled() and not source.isFailed()
|
|
|
|
|
|
|
|
def _addSource(self, key, source: SdrSource):
|
|
|
|
if self.isAvailable(source):
|
|
|
|
self._layer[key] = source
|
|
|
|
self.handlers[key] = SourceStateHandler(self._layer, key, source)
|
|
|
|
source.addClient(self.handlers[key])
|
|
|
|
|
|
|
|
def _removeSource(self, key):
|
|
|
|
self.handlers[key].selfDestruct()
|
|
|
|
del self.handlers[key]
|
|
|
|
if key in self._layer:
|
|
|
|
del self._layer[key]
|
|
|
|
|
|
|
|
|
2021-03-18 17:59:38 +00:00
|
|
|
class SdrService(object):
|
|
|
|
sources = None
|
2021-03-20 00:10:18 +00:00
|
|
|
activeSources = None
|
2019-12-21 19:58:28 +00:00
|
|
|
|
|
|
|
@staticmethod
|
2019-12-23 20:12:28 +00:00
|
|
|
def getFirstSource():
|
2021-03-18 20:53:59 +00:00
|
|
|
sources = SdrService.getActiveSources()
|
2019-12-21 19:58:28 +00:00
|
|
|
if not sources:
|
|
|
|
return None
|
2019-12-23 20:12:28 +00:00
|
|
|
# TODO: configure default sdr in config? right now it will pick the first one off the list.
|
|
|
|
return sources[list(sources.keys())[0]]
|
2019-12-21 19:58:28 +00:00
|
|
|
|
2019-12-23 20:12:28 +00:00
|
|
|
@staticmethod
|
|
|
|
def getSource(id):
|
2021-03-18 20:53:59 +00:00
|
|
|
sources = SdrService.getActiveSources()
|
2019-12-23 20:12:28 +00:00
|
|
|
if not sources:
|
|
|
|
return None
|
2021-03-18 17:59:38 +00:00
|
|
|
if id not in sources:
|
2019-12-21 19:58:28 +00:00
|
|
|
return None
|
|
|
|
return sources[id]
|
|
|
|
|
|
|
|
@staticmethod
|
2021-03-18 20:53:59 +00:00
|
|
|
def getAllSources():
|
2021-03-18 17:59:38 +00:00
|
|
|
if SdrService.sources is None:
|
|
|
|
SdrService.sources = MappedSdrSources(Config.get()["sdrs"])
|
2021-03-18 20:53:59 +00:00
|
|
|
return SdrService.sources
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def getActiveSources():
|
2021-03-20 00:10:18 +00:00
|
|
|
if SdrService.activeSources is None:
|
|
|
|
SdrService.activeSources = ActiveSdrSources(SdrService.getAllSources())
|
|
|
|
return SdrService.activeSources
|