Trying to integrate with rtl_433 for ISM signal decoding.

This commit is contained in:
Marat Fayzullin 2023-05-27 23:54:44 -04:00
parent 6079c2e1e9
commit 768b1f0099
7 changed files with 216 additions and 0 deletions

28
csdr/chain/rtl433.py Normal file
View File

@ -0,0 +1,28 @@
from csdr.chain.demodulator import ServiceDemodulator, DialFrequencyReceiver
from csdr.module.rtl433 import Rtl433Module
from pycsdr.modules import Convert, Agc
from pycsdr.types import Format
from owrx.rtl433 import Rtl433Parser
class Rtl433Demodulator(ServiceDemodulator, DialFrequencyReceiver):
def __init__(self):
self.sampleRate = 24000
self.parser = Rtl433Parser()
workers = [
Agc(Format.COMPLEX_FLOAT),
Convert(Format.COMPLEX_FLOAT, Format.COMPLEX_SHORT),
Rtl433Module(self.sampleRate),
self.parser,
]
# 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:
self.parser.setDialFrequency(frequency)

22
csdr/module/rtl433.py Normal file
View File

@ -0,0 +1,22 @@
from pycsdr.types import Format
from csdr.module import PopenModule
class Rtl433Module(PopenModule):
def __init__(self, sampleRate: int = 24000):
self.sampleRate = sampleRate
super().__init__()
def getCommand(self):
return [
"rtl_433", "-r", "cs16:-", "-f", "0", "-s", str(self.sampleRate),
"-R", "-80", "-R", "-149", "-R", "-154", "-R", "-160",
"-R", "-161", "-R", "-167", "-R", "-178",
]
def getInputFormat(self) -> Format:
return Format.COMPLEX_SHORT
def getOutputFormat(self) -> Format:
return Format.CHAR

View File

@ -1390,6 +1390,7 @@ img.openwebrx-mirror-img
#openwebrx-panel-digimodes[data-mode="sstv"] #openwebrx-digimode-select-channel,
#openwebrx-panel-digimodes[data-mode="fax"] #openwebrx-digimode-select-channel,
#openwebrx-panel-digimodes[data-mode="selcall"] #openwebrx-digimode-select-channel
#openwebrx-panel-digimodes[data-mode="ism"] #openwebrx-digimode-select-channel
{
display: none;
}

View File

@ -643,6 +643,9 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient)
elif mod == "fax":
from csdr.chain.digimodes import FaxDemodulator
return FaxDemodulator()
elif mod == "ism":
from csdr.chain.rtl433 import Rtl433Demodulator
return Rtl433Demodulator()
def setSecondaryDemodulator(self, mod):
demodulator = self._getSecondaryDemodulator(mod)

View File

@ -88,6 +88,7 @@ class FeatureDetector(object):
"js8call": ["js8", "js8py"],
"drm": ["dream"],
"png": ["imagemagick"],
"ism": ["rtl433"],
}
def feature_availability(self):
@ -587,3 +588,11 @@ class FeatureDetector(object):
"""
return self.command_is_runnable("multimon-ng --help")
def has_rtl433(self):
"""
OpenWebRX uses the [rtl_433](https://github.com/merbanan/rtl_433) decoder suite to decode various
instrumentation signals. Rtl_433 is available from the package manager on many
distributions, or you can compile it from source.
"""
return self.command_is_runnable("rtl_433 --help")

View File

@ -187,6 +187,14 @@ class Modes(object):
requirements=["selcall"],
squelch=True
),
DigitalMode(
"ism",
"ISM",
underlying=["nfm"],
bandpass=Bandpass(-6250, 6250),
requirements=["ism"],
squelch=False
),
]
@staticmethod

145
owrx/rtl433.py Normal file
View File

@ -0,0 +1,145 @@
from owrx.storage import Storage
from csdr.module import ThreadModule
from pycsdr.types import Format
from datetime import datetime
import pickle
import os
import re
import logging
logger = logging.getLogger(__name__)
class Rtl433Parser(ThreadModule):
def __init__(self, filePrefix: str = "ISM", service: bool = False):
self.service = service
self.frequency = 0
self.data = bytearray(b'')
self.filePfx = filePrefix
self.file = None
self.maxLines = 10000
self.cntLines = 0
super().__init__()
def __del__(self):
# Close currently open file, if any
self.closeFile()
def closeFile(self):
if self.file is not None:
try:
logger.debug("Closing log file '%s'." % self.fileName)
self.file.close()
self.file = None
# Delete excessive files from storage
logger.debug("Performing storage cleanup...")
Storage().cleanStoredFiles()
except Exception as exptn:
logger.debug("Exception closing file: %s" % str(exptn))
self.file = None
def newFile(self, fileName):
self.closeFile()
try:
self.fileName = Storage().getFilePath(fileName + ".txt")
logger.debug("Opening log file '%s'..." % self.fileName)
self.file = open(self.fileName, "wb")
self.cntLines = 0
except Exception as exptn:
logger.debug("Exception opening file: %s" % str(exptn))
self.file = None
def writeFile(self, data):
# If no file open, create and open a new file
if self.file is None:
self.newFile(Storage().makeFileName(self.filePfx+"-{0}", self.frequency))
# If file open now...
if self.file is not None:
# Write new line into the file
try:
self.file.write(data)
except Exception:
pass
# No more than maxLines per file
self.cntLines = self.cntLines + 1
if self.cntLines >= self.maxLines:
self.closeFile()
def getInputFormat(self) -> Format:
return Format.CHAR
def getOutputFormat(self) -> Format:
return Format.CHAR
def setDialFrequency(self, frequency: int) -> None:
self.frequency = frequency
def myName(self):
return "%s%s" % (
"Service" if self.service else "Client",
" at %dkHz" % (self.frequency // 1000) if self.frequency>0 else ""
)
def parse(self, msg: str):
# By default, do not parse, just return the string
return msg
def run(self):
logger.debug("%s starting..." % self.myName())
# Run while there is input data
while self.doRun:
# Read input data
inp = self.reader.read()
# Terminate if no input data
if inp is None:
logger.debug("%s exiting..." % self.myName())
self.doRun = False
break
# Add read data to the buffer
self.data = self.data + inp.tobytes()
# Process buffer contents
out = self.process()
# Keep processing while there is input to parse
while out is not None:
if len(out)>0:
if isinstance(out, bytes):
self.writer.write(out)
elif isinstance(out, str):
self.writer.write(bytes(out, 'utf-8'))
else:
self.writer.write(pickle.dumps(out))
out = self.process()
def process(self):
# No result yet
out = None
# Search for end-of-line
eol = self.data.find(b'\n')
# If found end-of-line...
if eol>=0:
try:
msg = self.data[0:eol].decode(encoding="utf-8", errors="replace")
logger.debug("%s: %s" % (self.myName(), msg))
# If running as a service...
if self.service:
# Write message into open log file, including end-of-line
self.writeFile(self.data[0:eol+1])
# Empty result
out = {}
else:
# Let parse() function do its thing
out = self.parse(msg)
except Exception as exptn:
logger.debug("%s: Exception parsing: %s" % (self.myName(), str(exptn)))
# Remove parsed message from input, including end-of-line
del self.data[0:eol+1]
# Return parsed result or None if no result yet
return out