def __init__(self, id, props): self.id = id self.commandMapper = None self.props = PropertyStack() # layer 0 reserved for profile properties self.props.addLayer(1, props) self.props.addLayer(2, Config.get()) self.sdrProps = self.props.filter(*self.getEventNames()) self.profile_id = None self.activateProfile() self.wireEvents() if "port" in props and props["port"] is not None: self.port = props["port"] else: self.port = getAvailablePort() self.monitor = None self.clients = [] self.spectrumClients = [] self.spectrumThread = None self.process = None self.modificationLock = threading.Lock() self.failed = False self.state = SdrSource.STATE_STOPPED self.busyState = SdrSource.BUSYSTATE_IDLE if self.isAlwaysOn(): self.start()
def handleSdrAvailable(self): # send initial config self.getDsp().setProperties(self.connectionProperties) stack = PropertyStack() stack.addLayer(0, self.sdr.getProps()) stack.addLayer(1, Config.get()) configProps = stack.filter(*OpenWebRxReceiverClient.config_keys) def sendConfig(key, value): config = configProps.__dict__() # TODO mathematical properties? hmmmm config["start_offset_freq"] = configProps["start_freq"] - configProps["center_freq"] # TODO this is a hack to support multiple sdrs config["sdr_id"] = self.sdr.getId() self.write_config(config) cf = configProps["center_freq"] srh = configProps["samp_rate"] / 2 frequencyRange = (cf - srh, cf + srh) self.write_dial_frequendies(Bandplan.getSharedInstance().collectDialFrequencies(frequencyRange)) bookmarks = [b.__dict__() for b in Bookmarks.getSharedInstance().getBookmarks(frequencyRange)] self.write_bookmarks(bookmarks) self.configSub = configProps.wire(sendConfig) sendConfig(None, None) self.__sendProfiles() self.sdr.addSpectrumClient(self)
def testPropertyChange(self): layer = PropertyLayer() stack = PropertyStack() stack.addLayer(0, layer) mock = Mock() stack.wire(mock.method) layer["testkey"] = "testvalue" mock.method.assert_called_once_with("testkey", "testvalue")
def testDeletionEvent(self): ps = PropertyStack() pm = PropertyLayer(testkey="testvalue") ps.addLayer(0, pm) mock = Mock() ps.wire(mock.method) del ps["testkey"] mock.method.assert_called_once_with({"testkey": PropertyDeleted})
def testPriorityFallback(self): om = PropertyStack() low_pm = PropertyLayer() high_pm = PropertyLayer() low_pm["testkey"] = "low value" om.addLayer(1, low_pm) om.addLayer(0, high_pm) self.assertEqual(om["testkey"], "low value")
def testWritesToExpectedLayer(self): om = PropertyStack() low_pm = PropertyLayer() high_pm = PropertyLayer() low_pm["testkey"] = "low value" om.addLayer(1, low_pm) om.addLayer(0, high_pm) om["testkey"] = "new value" self.assertEqual(low_pm["testkey"], "new value")
def testChangeEventWhenKeyDeleted(self): ps = PropertyStack() low_pm = PropertyLayer(testkey="lowvalue") high_pm = PropertyLayer(testkey="highvalue") ps.addLayer(0, high_pm) ps.addLayer(1, low_pm) mock = Mock() ps.wire(mock.method) del high_pm["testkey"] mock.method.assert_called_once_with({"testkey": "lowvalue"})
def testDeletionWithSecondLayer(self): ps = PropertyStack() low_pm = PropertyLayer(testkey="testvalue") high_pm = PropertyLayer() ps.addLayer(0, high_pm) ps.addLayer(1, low_pm) mock = Mock() ps.wire(mock.method) del low_pm["testkey"] mock.method.assert_called_once_with({"testkey": PropertyDeleted})
def testLayerRemoval(self): om = PropertyStack() low_pm = PropertyLayer() high_pm = PropertyLayer() low_pm["testkey"] = "low value" high_pm["testkey"] = "high value" om.addLayer(1, low_pm) om.addLayer(0, high_pm) self.assertEqual(om["testkey"], "high value") om.removeLayer(high_pm) self.assertEqual(om["testkey"], "low value")
def testEventOnLayerWithNewProperty(self): low_layer = PropertyLayer() low_layer["existingkey"] = "existing value" stack = PropertyStack() stack.addLayer(1, low_layer) mock = Mock() stack.wireProperty("newkey", mock.method) high_layer = PropertyLayer() high_layer["newkey"] = "new value" stack.addLayer(0, high_layer) mock.method.assert_called_once_with("new value")
def testNoEventOnExistingValue(self): low_layer = PropertyLayer() low_layer["testkey"] = "same value" stack = PropertyStack() stack.addLayer(1, low_layer) mock = Mock() stack.wireProperty("testkey", mock.method) mock.reset_mock() high_layer = PropertyLayer() high_layer["testkey"] = "same value" stack.addLayer(0, high_layer) mock.method.assert_not_called()
def testPropertyEventOnLayerAdd(self): low_layer = PropertyLayer() low_layer["testkey"] = "low value" stack = PropertyStack() stack.addLayer(1, low_layer) mock = Mock() stack.wireProperty("testkey", mock.method) mock.reset_mock() high_layer = PropertyLayer() high_layer["testkey"] = "high value" stack.addLayer(0, high_layer) mock.method.assert_called_once_with("high value")
def testUnwiresEventsOnRemoval(self): layer = PropertyLayer() layer["testkey"] = "before" stack = PropertyStack() stack.addLayer(0, layer) mock = Mock() stack.wire(mock.method) stack.removeLayer(layer) mock.method.assert_called_once_with("testkey", None) mock.reset_mock() layer["testkey"] = "after" mock.method.assert_not_called()
def __init__(self, sdrSource): self.sdrSource = sdrSource super().__init__() stack = PropertyStack() stack.addLayer(0, self.sdrSource.props) stack.addLayer(1, Config.get()) self.props = props = stack.filter( "samp_rate", "fft_size", "fft_fps", "fft_voverlap_factor", "fft_compression", "csdr_dynamic_bufsize", "csdr_print_bufsizes", "csdr_through", "temporary_directory", ) self.dsp = dsp = csdr.dsp(self) dsp.nc_port = self.sdrSource.getPort() dsp.set_demodulator("fft") def set_fft_averages(key, value): samp_rate = props["samp_rate"] fft_size = props["fft_size"] fft_fps = props["fft_fps"] fft_voverlap_factor = props["fft_voverlap_factor"] dsp.set_fft_averages( int(round(1.0 * samp_rate / fft_size / fft_fps / (1.0 - fft_voverlap_factor))) if fft_voverlap_factor > 0 else 0 ) self.subscriptions = [ props.wireProperty("samp_rate", dsp.set_samp_rate), props.wireProperty("fft_size", dsp.set_fft_size), props.wireProperty("fft_fps", dsp.set_fft_fps), props.wireProperty("fft_compression", dsp.set_fft_compression), props.wireProperty("temporary_directory", dsp.set_temporary_directory), props.filter("samp_rate", "fft_size", "fft_fps", "fft_voverlap_factor").wire(set_fft_averages), ] set_fft_averages(None, None) dsp.csdr_dynamic_bufsize = props["csdr_dynamic_bufsize"] dsp.csdr_print_bufsizes = props["csdr_print_bufsizes"] dsp.csdr_through = props["csdr_through"] logger.debug("Spectrum thread initialized successfully.")
def testPropertyChangeEventPriority(self): low_layer = PropertyLayer() high_layer = PropertyLayer() low_layer["testkey"] = "initial low value" high_layer["testkey"] = "initial high value" stack = PropertyStack() stack.addLayer(1, low_layer) stack.addLayer(0, high_layer) mock = Mock() stack.wire(mock.method) low_layer["testkey"] = "modified low value" mock.method.assert_not_called() high_layer["testkey"] = "modified high value" mock.method.assert_called_once_with("testkey", "modified high value")
def testNoneOnKeyRemoval(self): low_layer = PropertyLayer() high_layer = PropertyLayer() stack = PropertyStack() stack.addLayer(1, low_layer) stack.addLayer(0, high_layer) low_layer["testkey"] = "low value" high_layer["testkey"] = "high value" high_layer["unique key"] = "unique value" mock = Mock() stack.wireProperty("unique key", mock.method) mock.method.assert_called_once_with("unique value") mock.reset_mock() stack.removeLayer(high_layer) mock.method.assert_called_once_with(None)
def testReplaceLayer(self): first_layer = PropertyLayer() first_layer["testkey"] = "old value" second_layer = PropertyLayer() second_layer["testkey"] = "new value" stack = PropertyStack() stack.addLayer(0, first_layer) mock = Mock() stack.wireProperty("testkey", mock.method) mock.method.assert_called_once_with("old value") mock.reset_mock() stack.replaceLayer(0, second_layer) mock.method.assert_called_once_with("new value")
def __init__(self, id, props): self.id = id self.commandMapper = None self.props = PropertyStack() # layer 0 reserved for profile properties self.profileCarousel = SdrProfileCarousel(props) # prevent profile names from overriding the device name self.props.addLayer( 0, PropertyFilter(self.profileCarousel, ByLambda(lambda x: x != "name"))) # props from our device config self.props.addLayer(1, props) # the sdr_id is constant, so we put it in a separate layer # this is used to detect device changes, that are then sent to the client self.props.addLayer(2, PropertyLayer(sdr_id=id).readonly()) # finally, accept global config properties from the top-level config self.props.addLayer(3, Config.get()) self.sdrProps = self.props.filter(*self.getEventNames()) self.wireEvents() self.port = getAvailablePort() self.monitor = None self.clients = [] self.spectrumClients = [] self.spectrumThread = None self.spectrumLock = threading.Lock() self.process = None self.modificationLock = threading.Lock() self.state = SdrSourceState.STOPPED self.enabled = "enabled" not in props or props["enabled"] props.filter("enabled").wire(self._handleEnableChanged) self.failed = False self.busyState = SdrBusyState.IDLE self.validateProfiles() if self.isAlwaysOn() and self.isEnabled(): self.start()
def validateProfiles(self): props = PropertyStack() props.addLayer(1, self.props) for id, p in self.props["profiles"].items(): props.replaceLayer(0, p) if "center_freq" not in props: logger.warning('Profile "%s" does not specify a center_freq', id) continue if "samp_rate" not in props: logger.warning('Profile "%s" does not specify a samp_rate', id) continue if "start_freq" in props: start_freq = props["start_freq"] srh = props["samp_rate"] / 2 center_freq = props["center_freq"] if start_freq < center_freq - srh or start_freq > center_freq + srh: logger.warning('start_freq for profile "%s" is out of range', id)
def testReplaceLayerNoEventWhenValueUnchanged(self): fixed = PropertyLayer() fixed["testkey"] = "fixed value" first_layer = PropertyLayer() first_layer["testkey"] = "same value" second_layer = PropertyLayer() second_layer["testkey"] = "same value" stack = PropertyStack() stack.addLayer(1, fixed) stack.addLayer(0, first_layer) mock = Mock() stack.wire(mock.method) mock.method.assert_not_called() stack.replaceLayer(0, second_layer) mock.method.assert_not_called()
def setParams(self, params): config = Config.get() # allow direct configuration only if enabled in the config if "configurable_keys" not in config: return keys = config["configurable_keys"] if not keys: return # only the keys in the protected property manager can be overridden from the web stack = PropertyStack() stack.addLayer(0, self.sdr.getProps()) stack.addLayer(1, config) protected = stack.filter(*keys) for key, value in params.items(): try: protected[key] = value except KeyError: pass
def __init__(self, sdrSource): self.sdrSource = sdrSource super().__init__() stack = PropertyStack() stack.addLayer(0, self.sdrSource.props) stack.addLayer(1, Config.get()) self.props = stack.filter( "samp_rate", "fft_size", "fft_fps", "fft_voverlap_factor", "fft_compression", ) self.dsp = None self.reader = None self.subscriptions = [] logger.debug("Spectrum thread initialized successfully.")
def setupStack(self): stack = PropertyStack() # stack layer 0 reserved for sdr properties # stack.addLayer(0, self.sdr.getProps()) stack.addLayer(1, Config.get()) configProps = stack.filter(*OpenWebRxReceiverClient.sdr_config_keys) def sendConfig(changes=None): if changes is None: config = configProps.__dict__() else: # transform deletions into Nones config = { k: v if v is not PropertyDeleted else None for k, v in changes.items() } if ((changes is None or "start_freq" in changes or "center_freq" in changes) and "start_freq" in configProps and "center_freq" in configProps): config["start_offset_freq"] = configProps[ "start_freq"] - configProps["center_freq"] if (changes is None or "profile_id" in changes) and self.sdr is not None: config["sdr_id"] = self.sdr.getId() self.write_config(config) def sendBookmarks(*args): cf = configProps["center_freq"] srh = configProps["samp_rate"] / 2 dial_frequencies = [] bookmarks = [] if "center_freq" in configProps and "samp_rate" in configProps: frequencyRange = (cf - srh, cf + srh) dial_frequencies = Bandplan.getSharedInstance( ).collectDialFrequencies(frequencyRange) bookmarks = [ b.__dict__() for b in Bookmarks.getSharedInstance().getBookmarks(frequencyRange) ] self.write_dial_frequencies(dial_frequencies) self.write_bookmarks(bookmarks) def updateBookmarkSubscription(*args): if self.bookmarkSub is not None: self.bookmarkSub.cancel() if "center_freq" in configProps and "samp_rate" in configProps: cf = configProps["center_freq"] srh = configProps["samp_rate"] / 2 frequencyRange = (cf - srh, cf + srh) self.bookmarkSub = Bookmarks.getSharedInstance().subscribe( frequencyRange, sendBookmarks) sendBookmarks() self.configSubs.append(configProps.wire(sendConfig)) self.configSubs.append( stack.filter("center_freq", "samp_rate").wire(updateBookmarkSubscription)) # send initial config sendConfig() return stack
def __init__(self, handler, sdrSource): self.handler = handler self.sdrSource = sdrSource self.parsers = { "meta": MetaParser(self.handler), "wsjt_demod": WsjtParser(self.handler), "packet_demod": AprsParser(self.handler), "pocsag_demod": PocsagParser(self.handler), "js8_demod": Js8Parser(self.handler), } self.props = PropertyStack() # local demodulator properties not forwarded to the sdr # ensure strict validation since these can be set from the client # and are used to build executable commands validators = { "output_rate": "int", "hd_output_rate": "int", "squelch_level": "num", "secondary_mod": ModulationValidator(), "low_cut": "num", "high_cut": "num", "offset_freq": "int", "mod": ModulationValidator(), "secondary_offset_freq": "int", "dmr_filter": "int", } self.localProps = PropertyValidator( PropertyLayer().filter(*validators.keys()), validators) self.props.addLayer(0, self.localProps) # properties that we inherit from the sdr self.props.addLayer( 1, self.sdrSource.getProps().filter( "audio_compression", "fft_compression", "digimodes_fft_size", "csdr_dynamic_bufsize", "csdr_print_bufsizes", "csdr_through", "digimodes_enable", "samp_rate", "digital_voice_unvoiced_quality", "temporary_directory", "center_freq", "start_mod", "start_freq", "wfm_deemphasis_tau", )) self.dsp = csdr.dsp(self) self.dsp.nc_port = self.sdrSource.getPort() def set_low_cut(cut): bpf = self.dsp.get_bpf() bpf[0] = cut self.dsp.set_bpf(*bpf) def set_high_cut(cut): bpf = self.dsp.get_bpf() bpf[1] = cut self.dsp.set_bpf(*bpf) def set_dial_freq(key, value): if self.props["center_freq"] is None or self.props[ "offset_freq"] is None: return freq = self.props["center_freq"] + self.props["offset_freq"] for parser in self.parsers.values(): parser.setDialFrequency(freq) if "start_mod" in self.props: self.dsp.set_demodulator(self.props["start_mod"]) mode = Modes.findByModulation(self.props["start_mod"]) if mode and mode.bandpass: self.dsp.set_bpf(mode.bandpass.low_cut, mode.bandpass.high_cut) else: self.dsp.set_bpf(-4000, 4000) if "start_freq" in self.props and "center_freq" in self.props: self.dsp.set_offset_freq(self.props["start_freq"] - self.props["center_freq"]) else: self.dsp.set_offset_freq(0) self.subscriptions = [ self.props.wireProperty("audio_compression", self.dsp.set_audio_compression), self.props.wireProperty("fft_compression", self.dsp.set_fft_compression), self.props.wireProperty("digimodes_fft_size", self.dsp.set_secondary_fft_size), self.props.wireProperty("samp_rate", self.dsp.set_samp_rate), self.props.wireProperty("output_rate", self.dsp.set_output_rate), self.props.wireProperty("hd_output_rate", self.dsp.set_hd_output_rate), self.props.wireProperty("offset_freq", self.dsp.set_offset_freq), self.props.wireProperty("center_freq", self.dsp.set_center_freq), self.props.wireProperty("squelch_level", self.dsp.set_squelch_level), self.props.wireProperty("low_cut", set_low_cut), self.props.wireProperty("high_cut", set_high_cut), self.props.wireProperty("mod", self.dsp.set_demodulator), self.props.wireProperty("digital_voice_unvoiced_quality", self.dsp.set_unvoiced_quality), self.props.wireProperty("dmr_filter", self.dsp.set_dmr_filter), self.props.wireProperty("temporary_directory", self.dsp.set_temporary_directory), self.props.wireProperty("wfm_deemphasis_tau", self.dsp.set_wfm_deemphasis_tau), self.props.filter("center_freq", "offset_freq").wire(set_dial_freq), ] self.dsp.csdr_dynamic_bufsize = self.props["csdr_dynamic_bufsize"] self.dsp.csdr_print_bufsizes = self.props["csdr_print_bufsizes"] self.dsp.csdr_through = self.props["csdr_through"] if self.props["digimodes_enable"]: def set_secondary_mod(mod): if mod == False: mod = None self.dsp.set_secondary_demodulator(mod) if mod is not None: self.handler.write_secondary_dsp_config({ "secondary_fft_size": self.props["digimodes_fft_size"], "if_samp_rate": self.dsp.if_samp_rate(), "secondary_bw": self.dsp.secondary_bw(), }) self.subscriptions += [ self.props.wireProperty("secondary_mod", set_secondary_mod), self.props.wireProperty("secondary_offset_freq", self.dsp.set_secondary_offset_freq), ] self.startOnAvailable = False self.sdrSource.addClient(self) super().__init__()
def setSdr(self, id=None): while True: next = None if id is not None: next = SdrService.getSource(id) if next is None: next = SdrService.getFirstSource() if next is None: # exit condition: no sdrs available logger.warning("no more SDR devices available") self.handleNoSdrsAvailable() return # exit condition: no change if next == self.sdr: return self.stopDsp() if self.configSub is not None: self.configSub.cancel() self.configSub = None self.sdr = next self.getDsp() # found a working sdr, exit the loop if self.sdr.getState() != SdrSource.STATE_FAILED: break logger.warning('SDR device "%s" has failed, selecing new device', self.sdr.getName()) self.write_log_message('SDR device "{0}" has failed, selecting new device'.format(self.sdr.getName())) # send initial config self.getDsp().setProperties(self.connectionProperties) stack = PropertyStack() stack.addLayer(0, self.sdr.getProps()) stack.addLayer(1, Config.get()) configProps = stack.filter(*OpenWebRxReceiverClient.config_keys) def sendConfig(key, value): config = configProps.__dict__() # TODO mathematical properties? hmmmm config["start_offset_freq"] = configProps["start_freq"] - configProps["center_freq"] # TODO this is a hack to support multiple sdrs config["sdr_id"] = self.sdr.getId() self.write_config(config) cf = configProps["center_freq"] srh = configProps["samp_rate"] / 2 frequencyRange = (cf - srh, cf + srh) self.write_dial_frequendies(Bandplan.getSharedInstance().collectDialFrequencies(frequencyRange)) bookmarks = [b.__dict__() for b in Bookmarks.getSharedInstance().getBookmarks(frequencyRange)] self.write_bookmarks(bookmarks) self.configSub = configProps.wire(sendConfig) sendConfig(None, None) self.__sendProfiles() self.sdr.addSpectrumClient(self)
def addLayer(self, profile_id, profile): profile_stack = PropertyStack() profile_stack.addLayer(0, PropertyLayer(profile_id=profile_id).readonly()) profile_stack.addLayer(1, profile) super().addLayer(profile_id, profile_stack)
def testLayer(self): om = PropertyStack() pm = PropertyLayer() pm["testkey"] = "testvalue" om.addLayer(1, pm) self.assertEqual(om["testkey"], "testvalue")
def __init__(self, handler, sdrSource): self.handler = handler self.sdrSource = sdrSource self.props = PropertyStack() # current audio mode. should be "audio" or "hd_audio" depending on what demodulatur is in use. self.audioOutput = None # local demodulator properties not forwarded to the sdr # ensure strict validation since these can be set from the client # and are used to build executable commands validators = { "output_rate": "int", "hd_output_rate": "int", "squelch_level": "num", "secondary_mod": ModulationValidator(), "low_cut": "num", "high_cut": "num", "offset_freq": "int", "mod": ModulationValidator(), "secondary_offset_freq": "int", "dmr_filter": "int", } self.localProps = PropertyValidator( PropertyLayer().filter(*validators.keys()), validators) self.props.addLayer(0, self.localProps) # properties that we inherit from the sdr self.props.addLayer( 1, self.sdrSource.getProps().filter( "audio_compression", "fft_compression", "digimodes_fft_size", "samp_rate", "center_freq", "start_mod", "start_freq", "wfm_deemphasis_tau", "digital_voice_codecserver", ), ) # defaults for values that may not be set self.props.addLayer( 2, PropertyLayer( output_rate=12000, hd_output_rate=48000, digital_voice_codecserver="", ).readonly()) self.chain = ClientDemodulatorChain(self._getDemodulator("nfm"), self.props["samp_rate"], self.props["output_rate"], self.props["hd_output_rate"], self.props["audio_compression"], self) self.readers = {} if "start_mod" in self.props: mode = Modes.findByModulation(self.props["start_mod"]) if mode: self.setDemodulator(mode.get_modulation()) if isinstance(mode, DigitalMode): self.setSecondaryDemodulator(mode.modulation) if mode.bandpass: bpf = [mode.bandpass.low_cut, mode.bandpass.high_cut] self.chain.setBandpass(*bpf) else: # TODO modes should be mandatory self.setDemodulator(self.props["start_mod"]) if "start_freq" in self.props and "center_freq" in self.props: self.chain.setFrequencyOffset(self.props["start_freq"] - self.props["center_freq"]) else: self.chain.setFrequencyOffset(0) self.subscriptions = [ self.props.wireProperty("audio_compression", self.setAudioCompression), self.props.wireProperty("fft_compression", self.chain.setSecondaryFftCompression), self.props.wireProperty("fft_voverlap_factor", self.chain.setSecondaryFftOverlapFactor), self.props.wireProperty("fft_fps", self.chain.setSecondaryFftFps), self.props.wireProperty("digimodes_fft_size", self.setSecondaryFftSize), self.props.wireProperty("samp_rate", self.chain.setSampleRate), self.props.wireProperty("output_rate", self.chain.setOutputRate), self.props.wireProperty("hd_output_rate", self.chain.setHdOutputRate), self.props.wireProperty("offset_freq", self.chain.setFrequencyOffset), self.props.wireProperty("center_freq", self.chain.setCenterFrequency), self.props.wireProperty("squelch_level", self.chain.setSquelchLevel), self.props.wireProperty("low_cut", self.chain.setLowCut), self.props.wireProperty("high_cut", self.chain.setHighCut), self.props.wireProperty("mod", self.setDemodulator), self.props.wireProperty("dmr_filter", self.chain.setSlotFilter), 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), ] # wire power level output buffer = Buffer(Format.FLOAT) self.chain.setPowerWriter(buffer) self.wireOutput("smeter", buffer) # wire meta output buffer = Buffer(Format.CHAR) self.chain.setMetaWriter(buffer) self.wireOutput("meta", buffer) # wire secondary FFT buffer = Buffer(self.chain.getSecondaryFftOutputFormat()) self.chain.setSecondaryFftWriter(buffer) self.wireOutput("secondary_fft", buffer) # wire secondary demodulator buffer = Buffer(Format.CHAR) self.chain.setSecondaryWriter(buffer) self.wireOutput("secondary_demod", buffer) self.startOnAvailable = False self.sdrSource.addClient(self)