From aa6412930697581fcac6924e50e1d5cc24c2d967 Mon Sep 17 00:00:00 2001 From: Marat Fayzullin Date: Thu, 7 Mar 2024 18:25:08 -0500 Subject: [PATCH] Adding preliminary SatDump support (experts only!) --- csdr/chain/toolbox.py | 45 +++++++++++++++++++++++++++++++++++++++- csdr/module/toolbox.py | 26 +++++++++++++++++++++++ owrx/feature.py | 8 +++++++ owrx/modes.py | 20 ++++++++++++++++++ owrx/service/__init__.py | 6 ++++++ 5 files changed, 104 insertions(+), 1 deletion(-) diff --git a/csdr/chain/toolbox.py b/csdr/chain/toolbox.py index 41e6294c..f5cf4838 100644 --- a/csdr/chain/toolbox.py +++ b/csdr/chain/toolbox.py @@ -1,5 +1,5 @@ from csdr.chain.demodulator import ServiceDemodulator, DialFrequencyReceiver, FixedIfSampleRateChain -from csdr.module.toolbox import Rtl433Module, MultimonModule, DumpHfdlModule, DumpVdl2Module, Dump1090Module, AcarsDecModule, RedseaModule +from csdr.module.toolbox import Rtl433Module, MultimonModule, DumpHfdlModule, DumpVdl2Module, Dump1090Module, AcarsDecModule, RedseaModule, SatDumpModule from pycsdr.modules import FmDemod, AudioResampler, Convert, Agc, Squelch from pycsdr.types import Format from owrx.toolbox import TextParser, PageParser, SelCallParser, IsmParser, RdsParser @@ -210,3 +210,46 @@ class RdsDemodulator(ServiceDemodulator, DialFrequencyReceiver): def setDialFrequency(self, frequency: int) -> None: self.parser.setDialFrequency(frequency) + + +class NoaaAptDemodulator(ServiceDemodulator, DialFrequencyReceiver): + def __init__(self, satellite: int = 19, service: bool = False): + self.sampleRate = 50000 + workers = [ + SatDumpModule(mode = "noaa_apt", sampleRate = self.sampleRate, options = { + "satellite_number" : satellite, + "start_timestamp" : datetime.utcnow().timestamp() + }) + ] + # Connect all the workers + super().__init__(workers) + + def getFixedAudioRate(self) -> int: + return self.sampleRate + + def supportsSquelch(self) -> bool: + return False + + def setDialFrequency(self, frequency: int) -> None: + # TODO: Do something with the frequency or remove method + pass + + +class MeteorLrptDemodulator(ServiceDemodulator, DialFrequencyReceiver): + def __init__(self, service: bool = False): + self.sampleRate = 150000 + workers = [ + SatDumpModule(mode = "meteor_m2-x_lrpt", sampleRate = self.sampleRate) + ] + # Connect all the workers + super().__init__(workers) + + def getFixedAudioRate(self) -> int: + return self.sampleRate + + def supportsSquelch(self) -> bool: + return False + + def setDialFrequency(self, frequency: int) -> None: + # TODO: Do something with the frequency or remove method + pass diff --git a/csdr/module/toolbox.py b/csdr/module/toolbox.py index 1fbd922f..756e488f 100644 --- a/csdr/module/toolbox.py +++ b/csdr/module/toolbox.py @@ -174,3 +174,29 @@ class DablinModule(ExecModule): self.serviceId = serviceId self.setArgs(self._buildArgs()) self.restart() + + +class SatDumpModule(ExecModule): + def __init__(self, mode: str = "noaa_apt", sampleRate: int = 50000, frequency: int = 137000000, options = None): + # Make sure we have output folder + self.outFolder = "/tmp/satdump" + try: + os.makedirs(self.outFolder, exist_ok = True) + except: + self.outFolder = "/tmp" + # Compose command line + cmd = [ + "satdump", "live", mode, self.outFolder, + "--source", "file", "--file_path", "/dev/stdin", + "--samplerate", str(sampleRate), + "--frequency", str(frequency), + "--baseband_format", "f32", + "--finish_processing", + ] + # Add pipeline-specific options + if options: + for key in options.keys(): + cmd.append("--" + key) + cmd.append(str(options[key])) + # Create parent object + super().__init__(Format.COMPLEX_FLOAT, Format.CHAR, cmd) diff --git a/owrx/feature.py b/owrx/feature.py index 78f677ce..5632ffc7 100644 --- a/owrx/feature.py +++ b/owrx/feature.py @@ -94,6 +94,7 @@ class FeatureDetector(object): "selcall": ["multimon"], "rds": ["redsea"], "dab": ["csdreti", "dablin"], + "wxsat": ["satdump"], "png": ["imagemagick"], } @@ -714,3 +715,10 @@ class FeatureDetector(object): """ return self.command_is_runnable("multimon-ng --help") + def has_satdump(self): + """ + OpenWebRX uses [SatDump](https://github.com/SatDump/SatDump) software + suite to receive weather satellite transmissions. The `satdump` + package is available in some Linux distributions. + """ + return self.command_is_runnable("satdump --help") diff --git a/owrx/modes.py b/owrx/modes.py index 89ee5ded..4f030a3a 100644 --- a/owrx/modes.py +++ b/owrx/modes.py @@ -269,6 +269,26 @@ class Modes(object): squelch=False, secondaryFft=False ), + DigitalMode( + "noaa-apt", + "NOAA APT", + underlying=["empty"], + bandpass=Bandpass(-25000, 25000), + requirements=["wxsat"], + service=True, + squelch=False, + secondaryFft=False + ), + DigitalMode( + "meteor-lrpt", + "Meteor-M2 LRPT", + underlying=["empty"], + bandpass=Bandpass(-75000, 75000), + requirements=["wxsat"], + service=True, + squelch=False, + secondaryFft=False + ), ] @staticmethod diff --git a/owrx/service/__init__.py b/owrx/service/__init__.py index 2fe7a3c2..fa4fe32c 100644 --- a/owrx/service/__init__.py +++ b/owrx/service/__init__.py @@ -347,6 +347,12 @@ class ServiceHandler(SdrSourceEventClient): elif mod == "adsb": from csdr.chain.toolbox import AdsbDemodulator return AdsbDemodulator(service=True) + elif mod == "noaa-apt": + from csdr.chain.toolbox import NoaaAptDemodulator + return NoaaAptDemodulator(service=True) + elif mod == "meteor-lrpt": + from csdr.chain.toolbox import MeteorLrptDemodulator + return MeteorLrptDemodulator(service=True) raise ValueError("unsupported service modulation: {}".format(mod))