172 lines
6.0 KiB
Python
172 lines
6.0 KiB
Python
from pycsdr.modules import ExecModule
|
|
from pycsdr.types import Format
|
|
from csdr.module import PopenModule
|
|
from owrx.config import Config
|
|
import os
|
|
|
|
class Rtl433Module(ExecModule):
|
|
def __init__(self, sampleRate: int = 250000, jsonOutput: bool = False):
|
|
cmd = [
|
|
"rtl_433", "-r", "cs16:-", "-s", str(sampleRate),
|
|
"-M", "time:unix" if jsonOutput else "time:utc",
|
|
"-F", "json" if jsonOutput else "kv",
|
|
"-A",
|
|
]
|
|
super().__init__(Format.COMPLEX_SHORT, Format.CHAR, cmd)
|
|
|
|
|
|
class MultimonModule(ExecModule):
|
|
def __init__(self, decoders: list[str]):
|
|
pm = Config.get()
|
|
cmd = ["multimon-ng", "-", "-v0", "-C", pm["paging_charset"], "-c"]
|
|
for x in decoders:
|
|
cmd += ["-a", x]
|
|
super().__init__(Format.SHORT, Format.CHAR, cmd)
|
|
|
|
|
|
class DumpHfdlModule(ExecModule):
|
|
def __init__(self, sampleRate: int = 12000, jsonOutput: bool = False):
|
|
cmd = [
|
|
"dumphfdl", "--iq-file", "-", "--sample-format", "CF32",
|
|
"--sample-rate", str(sampleRate), "--output",
|
|
"decoded:%s:file:path=-" % ("json" if jsonOutput else "text"),
|
|
"--utc", "--centerfreq", "0", "0"
|
|
]
|
|
super().__init__(Format.COMPLEX_FLOAT, Format.CHAR, cmd)
|
|
|
|
|
|
class DumpVdl2Module(ExecModule):
|
|
def __init__(self, sampleRate: int = 105000, jsonOutput: bool = False):
|
|
cmd = [
|
|
"dumpvdl2", "--iq-file", "-", "--sample-format", "S16_LE",
|
|
"--oversample", str(sampleRate // 105000), "--output",
|
|
"decoded:%s:file:path=-" % ("json" if jsonOutput else "text"),
|
|
"--decode-fragments", "--utc"
|
|
]
|
|
super().__init__(Format.COMPLEX_SHORT, Format.CHAR, cmd)
|
|
|
|
|
|
class Dump1090Module(ExecModule):
|
|
def __init__(self, rawOutput: bool = False, jsonFolder: str = None):
|
|
pm = Config.get()
|
|
lat = pm["receiver_gps"]["lat"]
|
|
lon = pm["receiver_gps"]["lon"]
|
|
cmd = [
|
|
"dump1090", "--ifile", "-", "--iformat", "SC16",
|
|
"--lat", str(lat), "--lon", str(lon),
|
|
"--modeac", "--metric"
|
|
]
|
|
# If JSON files folder supplied, use that, disable STDOUT output
|
|
if jsonFolder is not None:
|
|
try:
|
|
os.makedirs(jsonFolder, exist_ok = True)
|
|
cmd += [ "--quiet", "--write-json", jsonFolder ]
|
|
except:
|
|
self.jsonFolder = None
|
|
pass
|
|
# RAW STDOUT output only makes sense if we are not using JSON
|
|
if rawOutput and jsonFolder is None:
|
|
cmd += [ "--raw" ]
|
|
super().__init__(Format.COMPLEX_SHORT, Format.CHAR, cmd)
|
|
|
|
|
|
class WavFileModule(PopenModule):
|
|
def getInputFormat(self) -> Format:
|
|
return Format.SHORT
|
|
|
|
def start(self):
|
|
# Create process and pumps
|
|
super().start()
|
|
# Created simulated .WAV file header
|
|
byteRate = (self.sampleRate * 16 * 1) >> 3
|
|
header = bytearray(44)
|
|
header[0:3] = b"RIFF"
|
|
header[4:7] = bytes([36, 0xFF, 0xFF, 0xFF])
|
|
header[8:11] = b"WAVE"
|
|
header[12:15] = b"fmt "
|
|
header[16] = 16 # Chunk size
|
|
header[20] = 1 # Format (PCM)
|
|
header[22] = 1 # Number of channels (1)
|
|
header[24] = self.sampleRate & 0xFF
|
|
header[25] = (self.sampleRate >> 8) & 0xFF
|
|
header[26] = (self.sampleRate >> 16) & 0xFF
|
|
header[27] = (self.sampleRate >> 24) & 0xFF
|
|
header[28] = byteRate & 0xFF
|
|
header[29] = (byteRate >> 8) & 0xFF
|
|
header[30] = (byteRate >> 16) & 0xFF
|
|
header[31] = (byteRate >> 24) & 0xFF
|
|
header[32] = 2 # Block alignment (2 bytes)
|
|
header[34] = 16 # Bits per sample (16)
|
|
header[36:39] = b"data"
|
|
header[40:43] = bytes([0, 0xFF, 0xFF, 0xFF])
|
|
# Send .WAV file header to the process
|
|
self.process.stdin.write(header)
|
|
|
|
|
|
class AcarsDecModule(WavFileModule):
|
|
def __init__(self, sampleRate: int = 12500, jsonOutput: bool = False):
|
|
self.sampleRate = sampleRate
|
|
self.jsonOutput = jsonOutput
|
|
super().__init__()
|
|
|
|
def getCommand(self):
|
|
return [
|
|
"acarsdec", "-f", "/dev/stdin",
|
|
"-o", str(4 if self.jsonOutput else 1)
|
|
]
|
|
|
|
def getOutputFormat(self) -> Format:
|
|
return Format.CHAR
|
|
|
|
|
|
class RedseaModule(ExecModule):
|
|
def __init__(self, sampleRate: int = 171000, rbds: bool = False):
|
|
cmd = [ "redsea", "--input", "mpx", "--samplerate", str(sampleRate) ]
|
|
if rbds:
|
|
cmd += ["--rbds"]
|
|
super().__init__(Format.SHORT, Format.CHAR, cmd)
|
|
|
|
|
|
class DablinModule(ExecModule):
|
|
def __init__(self):
|
|
self.serviceId = 0
|
|
super().__init__(
|
|
Format.CHAR,
|
|
Format.FLOAT,
|
|
self._buildArgs()
|
|
)
|
|
|
|
def _buildArgs(self):
|
|
return ["dablin", "-p", "-s", "{:#06x}".format(self.serviceId)]
|
|
|
|
def setDabServiceId(self, serviceId: int) -> None:
|
|
self.serviceId = serviceId
|
|
self.setArgs(self._buildArgs())
|
|
self.restart()
|
|
|
|
|
|
class SatDumpModule(ExecModule):
|
|
def __init__(self, mode: str = "noaa_apt", sampleRate: int = 50000, frequency: int = 137100000, outFolder: str = "/tmp/satdump", options = None):
|
|
# Make sure we have output folder
|
|
try:
|
|
os.makedirs(outFolder, exist_ok = True)
|
|
except:
|
|
outFolder = "/tmp"
|
|
# Compose command line
|
|
cmd = [
|
|
"satdump", "live", mode, outFolder,
|
|
"--source", "file", "--file_path", "/dev/stdin",
|
|
"--samplerate", str(sampleRate),
|
|
"--frequency", str(frequency),
|
|
"--baseband_format", "f32",
|
|
# Not trying to decode actual imagery for now, leaving .CADU file instead
|
|
# "--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, doNotKill=True)
|