def setParams(self, params): # allow direct configuration only if enabled in the config keys = PropertyManager.getSharedInstance()["configurable_keys"] if not keys: return # only the keys in the protected property manager can be overridden from the web protected = ( self.sdr.getProps() .collect(*keys) .defaults(PropertyManager.getSharedInstance()) ) for key, value in params.items(): protected[key] = value
def __init__(self, conn): super().__init__(conn) pm = PropertyManager.getSharedInstance() self.write_config(pm.collect("google_maps_api_key", "receiver_gps", "map_position_retention_time").__dict__()) Map.getSharedInstance().addClient(self)
def start(): if not PropertyManager.getSharedInstance()["services_enabled"]: return for source in SdrService.getSources().values(): props = source.getProps() if "services" not in props or props["services"] is not False: Services.handlers.append(ServiceHandler(source))
def __init__(self, id, props): self.id = id self.props = props self.profile_id = None self.activateProfile() self.rtlProps = self.props.collect(*self.getEventNames()).defaults(PropertyManager.getSharedInstance()) self.wireEvents() self.commandMapper = None 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 getConfig(self, port, is_service): pm = PropertyManager.getSharedInstance() config = """ ACHANNELS 1 ADEVICE stdin null CHANNEL 0 MYCALL {callsign} MODEM 1200 KISSPORT {port} AGWPORT off """.format(port=port, callsign=pm["aprs_callsign"]) if is_service and pm["aprs_igate_enabled"]: config += """ IGSERVER {server} IGLOGIN {callsign} {password} """.format(server=pm["aprs_igate_server"], callsign=pm["aprs_callsign"], password=pm["aprs_igate_password"]) if pm["aprs_igate_beacon"]: (lat, lon) = pm["receiver_gps"] lat = "{0}^{1:.2f}{2}".format(int(lat), (lat - int(lat)) * 60, "N" if lat > 0 else "S") lon = "{0}^{1:.2f}{2}".format(int(lon), (lon - int(lon)) * 60, "E" if lon > 0 else "W") config += """ PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat={lat} long={lon} comment="OpenWebRX APRS gateway" """.format(lat=lat, lon=lon) return config
def loadProps(): if SdrService.sdrProps is None: pm = PropertyManager.getSharedInstance() featureDetector = FeatureDetector() def loadIntoPropertyManager(dict: dict): propertyManager = PropertyManager() for (name, value) in dict.items(): propertyManager[name] = value return propertyManager def sdrTypeAvailable(value): try: if not featureDetector.is_available(value["type"]): logger.error( 'The RTL source type "{0}" is not available. please check requirements.' .format(value["type"])) return False return True except UnknownFeatureException: logger.error( 'The RTL source type "{0}" is invalid. Please check your configuration' .format(value["type"])) return False # transform all dictionary items into PropertyManager object, filtering out unavailable ones SdrService.sdrProps = { name: loadIntoPropertyManager(value) for (name, value) in pm["sdrs"].items() if sdrTypeAvailable(value) } logger.info("SDR sources loaded. Available SDRs: {0}".format( ", ".join( map(lambda x: x["name"], SdrService.sdrProps.values()))))
def getSharedInstance(): with PskReporter.creationLock: if PskReporter.sharedInstance is None: if PropertyManager.getSharedInstance()["pskreporter_enabled"]: PskReporter.sharedInstance = PskReporter() else: PskReporter.sharedInstance = PskReporterDummy() return PskReporter.sharedInstance
def getSharedInstance(): with WsjtQueue.creationLock: if WsjtQueue.sharedInstance is None: pm = PropertyManager.getSharedInstance() WsjtQueue.sharedInstance = WsjtQueue( maxsize=pm["wsjt_queue_length"], workers=pm["wsjt_queue_workers"]) return WsjtQueue.sharedInstance
def __init__(self, source): self.source = source self.tmp_dir = PropertyManager.getSharedInstance( )["temporary_directory"] (self.wavefilename, self.wavefile) = self.getWaveFile() self.switchingLock = threading.Lock() self.timer = None (self.outputReader, self.outputWriter) = Pipe() self.doRun = True super().__init__()
def decoding_depth(self, mode): pm = PropertyManager.getSharedInstance() # mode-specific setting? if "wsjt_decoding_depths" in pm and mode in pm["wsjt_decoding_depths"]: return pm["wsjt_decoding_depths"][mode] # return global default if "wsjt_decoding_depth" in pm: return pm["wsjt_decoding_depth"] # default when no setting is provided return 3
def removeOldPositions(self): pm = PropertyManager.getSharedInstance() retention = timedelta(seconds=pm["map_position_retention_time"]) cutoff = datetime.now() - retention to_be_removed = [ callsign for (callsign, pos) in self.positions.items() if pos["updated"] < cutoff ] for callsign in to_be_removed: self.removeLocation(callsign)
def command_is_runnable(self, command): tmp_dir = PropertyManager.getSharedInstance()["temporary_directory"] cmd = shlex.split(command) try: process = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=tmp_dir) return process.wait() != 32512 except FileNotFoundError: return False
def getReceiverInformation(self): pm = PropertyManager.getSharedInstance() callsign = pm["pskreporter_callsign"] locator = Locator.fromCoordinates(pm["receiver_gps"]) decodingSoftware = "OpenWebRX " + openwebrx_version body = [ b for s in [callsign, locator, decodingSoftware] for b in self.encodeString(s) ] body = self.pad(body, 4) body = bytes(Uploader.receieverDelimiter + list((len(body) + 4).to_bytes(2, "big")) + body) return body
def __init__(self, sdrSource): self.sdrSource = sdrSource super().__init__() self.props = props = self.sdrSource.props.collect( "samp_rate", "fft_size", "fft_fps", "fft_voverlap_factor", "fft_compression", "csdr_dynamic_bufsize", "csdr_print_bufsizes", "csdr_through", "temporary_directory", ).defaults(PropertyManager.getSharedInstance()) 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.getProperty("samp_rate").wire(dsp.set_samp_rate), props.getProperty("fft_size").wire(dsp.set_fft_size), props.getProperty("fft_fps").wire(dsp.set_fft_fps), props.getProperty("fft_compression").wire(dsp.set_fft_compression), props.getProperty("temporary_directory").wire( dsp.set_temporary_directory), props.collect("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 enrich(self, meta): if not PropertyManager.getSharedInstance()["digital_voice_dmr_id_lookup"]: return None if not "source" in meta: return None id = meta["source"] cache = DmrCache.getSharedInstance() if not cache.isValid(id): if not id in self.threads: self.threads[id] = threading.Thread(target=self.downloadRadioIdData, args=[id], daemon=True) self.threads[id].start() return None data = cache.get(id) if "count" in data and data["count"] > 0 and "results" in data: return data["results"][0] return None
def isSupported(self, mode): # TODO this should be in a more central place (the frontend also needs this) requirements = { "ft8": "wsjt-x", "ft4": "wsjt-x", "jt65": "wsjt-x", "jt9": "wsjt-x", "wspr": "wsjt-x", "packet": "packet", } fd = FeatureDetector() # this looks overly complicated... but i'd like modes with no requirements to be always available without # being listed in the hash above unavailable = [mode for mode, req in requirements.items() if not fd.is_available(req)] configured = PropertyManager.getSharedInstance()["services_decoders"] available = [mode for mode in configured if mode not in unavailable] return mode in available
def getSunTimes(self, date): pm = PropertyManager.getSharedInstance() lat, lng = pm["receiver_gps"] degtorad = math.pi / 180 radtodeg = 180 / math.pi #Number of days since 01/01 days = date.timetuple().tm_yday # Longitudinal correction longCorr = 4 * lng # calibrate for solstice b = 2 * math.pi * (days - 81) / 365 # Equation of Time Correction eoTCorr = 9.87 * math.sin( 2 * b) - 7.53 * math.cos(b) - 1.5 * math.sin(b) # Solar correction solarCorr = longCorr + eoTCorr # Solar declination declination = math.asin(math.sin(23.45 * degtorad) * math.sin(b)) sunrise = 12 - math.acos( -math.tan(lat * degtorad) * math.tan(declination)) * radtodeg / 15 - solarCorr / 60 sunset = 12 + math.acos( -math.tan(lat * degtorad) * math.tan(declination)) * radtodeg / 15 - solarCorr / 60 midnight = datetime.combine(date, datetime.min.time()) sunrise = midnight + timedelta(hours=sunrise) sunset = midnight + timedelta(hours=sunset) logger.debug("for {date} sunrise: {sunrise} sunset {sunset}".format( date=date, sunrise=sunrise, sunset=sunset)) return sunrise, sunset
def __init__(self, conn): super().__init__(conn) self.dsp = None self.sdr = None self.configSub = None self.connectionProperties = {} try: ClientRegistry.getSharedInstance().addClient(self) except TooManyClientsException: self.write_backoff_message("Too many clients") self.close() raise pm = PropertyManager.getSharedInstance() self.setSdr() # send receiver info receiver_keys = [ "receiver_name", "receiver_location", "receiver_asl", "receiver_gps", "photo_title", "photo_desc", ] receiver_details = dict((key, pm.getPropertyValue(key)) for key in receiver_keys) receiver_details["locator"] = Locator.fromCoordinates(receiver_details["receiver_gps"]) self.write_receiver_details(receiver_details) self.__sendProfiles() features = FeatureDetector().feature_availability() self.write_features(features) CpuUsageThread.getSharedInstance().add_client(self)
def update(self): pm = PropertyManager.getSharedInstance().collect("server_hostname", "web_port", "sdrhu_key") data = parse.urlencode({ "url": "http://{server_hostname}:{web_port}".format(**pm.__dict__()), "apikey": pm["sdrhu_key"] }).encode() res = request.urlopen("https://sdr.hu/update", data=data) if res.getcode() < 200 or res.getcode() >= 300: logger.warning('sdr.hu update failed with error code %i', res.getcode()) return 2 returned = res.read().decode("utf-8") if "UPDATE:" not in returned: logger.warning("Update failed, your receiver cannot be listed on sdr.hu!") return 2 value = returned.split("UPDATE:")[1].split("\n", 1)[0] if value.startswith("SUCCESS"): logger.info("Update succeeded!") else: logger.warning("Update failed, your receiver cannot be listed on sdr.hu! Reason: %s", value) return 20
def main(): print(""" OpenWebRX - Open Source SDR Web App for Everyone! | for license see LICENSE file in the package _________________________________________________________________________________________________ Author contact info: Jakob Ketterl, DD5JFK <*****@*****.**> """) pm = PropertyManager.getSharedInstance().loadConfig() featureDetector = FeatureDetector() if not featureDetector.is_available("core"): print( "you are missing required dependencies to run openwebrx. " "please check that the following core requirements are installed:") print(", ".join(featureDetector.get_requirements("core"))) return # Get error messages about unknown / unavailable features as soon as possible SdrService.loadProps() if "sdrhu_key" in pm and pm["sdrhu_public_listing"]: updater = SdrHuUpdater() updater.start() Services.start() try: server = ThreadedHttpServer( ("0.0.0.0", pm.getPropertyValue("web_port")), RequestHandler) server.serve_forever() except KeyboardInterrupt: WebSocketConnection.closeAll() Services.stop() PskReporter.stop()
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 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.startDsp() # keep trying until we find a suitable SDR if self.sdr.getState() == SdrSource.STATE_FAILED: self.write_log_message('SDR device "{0}" has failed, selecting new device'.format(self.sdr.getName())) else: break # send initial config self.setDspProperties(self.connectionProperties) configProps = ( self.sdr.getProps() .collect(*OpenWebRxReceiverClient.config_keys) .defaults(PropertyManager.getSharedInstance()) ) def sendConfig(key, value): config = dict((key, configProps[key]) for key in OpenWebRxReceiverClient.config_keys) # 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 __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), } self.localProps = (self.sdrSource.getProps().collect( "audio_compression", "fft_compression", "digimodes_fft_size", "csdr_dynamic_bufsize", "csdr_print_bufsizes", "csdr_through", "digimodes_enable", "samp_rate", "digital_voice_unvoiced_quality", "dmr_filter", "temporary_directory", "center_freq", ).defaults(PropertyManager.getSharedInstance())) 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): freq = self.localProps["center_freq"] + self.localProps[ "offset_freq"] for parser in self.parsers.values(): parser.setDialFrequency(freq) self.subscriptions = [ self.localProps.getProperty("audio_compression").wire( self.dsp.set_audio_compression), self.localProps.getProperty("fft_compression").wire( self.dsp.set_fft_compression), self.localProps.getProperty("digimodes_fft_size").wire( self.dsp.set_secondary_fft_size), self.localProps.getProperty("samp_rate").wire( self.dsp.set_samp_rate), self.localProps.getProperty("output_rate").wire( self.dsp.set_output_rate), self.localProps.getProperty("offset_freq").wire( self.dsp.set_offset_freq), self.localProps.getProperty("squelch_level").wire( self.dsp.set_squelch_level), self.localProps.getProperty("low_cut").wire(set_low_cut), self.localProps.getProperty("high_cut").wire(set_high_cut), self.localProps.getProperty("mod").wire(self.dsp.set_demodulator), self.localProps.getProperty("digital_voice_unvoiced_quality").wire( self.dsp.set_unvoiced_quality), self.localProps.getProperty("dmr_filter").wire( self.dsp.set_dmr_filter), self.localProps.getProperty("temporary_directory").wire( self.dsp.set_temporary_directory), self.localProps.collect("center_freq", "offset_freq").wire(set_dial_freq), ] self.dsp.set_offset_freq(0) self.dsp.set_bpf(-4000, 4000) self.dsp.csdr_dynamic_bufsize = self.localProps["csdr_dynamic_bufsize"] self.dsp.csdr_print_bufsizes = self.localProps["csdr_print_bufsizes"] self.dsp.csdr_through = self.localProps["csdr_through"] if self.localProps["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.localProps["digimodes_fft_size"], "if_samp_rate": self.dsp.if_samp_rate(), "secondary_bw": self.dsp.secondary_bw(), }) self.subscriptions += [ self.localProps.getProperty("secondary_mod").wire( set_secondary_mod), self.localProps.getProperty("secondary_offset_freq").wire( self.dsp.set_secondary_offset_freq), ] self.sdrSource.addClient(self) super().__init__()
def addClient(self, client): pm = PropertyManager.getSharedInstance() if len(self.clients) >= pm["max_clients"]: raise TooManyClientsException() self.clients.append(client) self.broadcast()
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. """ from owrx.sdrhu import SdrHuUpdater from owrx.config import PropertyManager import logging logging.basicConfig( level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") logger = logging.getLogger(__name__) if __name__ == "__main__": pm = PropertyManager.getSharedInstance().loadConfig() if "sdrhu_public_listing" not in pm or not pm["sdrhu_public_listing"]: logger.error( 'Public listing on sdr.hu is not activated. Please check "sdrhu_public_listing" in your config.' ) exit(1) if "sdrhu_key" not in pm or pm["sdrhu_key"] is None or pm[ "sdrhu_key"] == "": logger.error('Missing "sdrhu_key" in your config. Aborting') exit(1) SdrHuUpdater().update()