Placing rtl433 and multimon integrations into a common "toolbox" module.
This commit is contained in:
parent
67789c07ef
commit
008189dc22
|
|
@ -1,29 +0,0 @@
|
||||||
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, service: bool = False):
|
|
||||||
self.sampleRate = 48000
|
|
||||||
self.parser = Rtl433Parser(service=service)
|
|
||||||
workers = [
|
|
||||||
Agc(Format.COMPLEX_FLOAT),
|
|
||||||
Convert(Format.COMPLEX_FLOAT, Format.COMPLEX_SHORT),
|
|
||||||
Rtl433Module(self.sampleRate, jsonOutput = not service),
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
@ -1,8 +1,31 @@
|
||||||
from csdr.chain.demodulator import ServiceDemodulator, DialFrequencyReceiver
|
from csdr.chain.demodulator import ServiceDemodulator, DialFrequencyReceiver
|
||||||
from csdr.module.multimon import MultimonModule
|
from csdr.module.toolbox import Rtl433Module, MultimonModule
|
||||||
from pycsdr.modules import FmDemod, AudioResampler, Convert, Squelch
|
from pycsdr.modules import FmDemod, AudioResampler, Convert, Agc, Squelch
|
||||||
from pycsdr.types import Format
|
from pycsdr.types import Format
|
||||||
from owrx.multimon import MultimonParser, PageParser, SelCallParser
|
from owrx.toolbox import TextParser, PageParser, SelCallParser, IsmParser
|
||||||
|
|
||||||
|
|
||||||
|
class IsmDemodulator(ServiceDemodulator, DialFrequencyReceiver):
|
||||||
|
def __init__(self, service: bool = False):
|
||||||
|
self.sampleRate = 48000
|
||||||
|
self.parser = IsmParser(service=service)
|
||||||
|
workers = [
|
||||||
|
Agc(Format.COMPLEX_FLOAT),
|
||||||
|
Convert(Format.COMPLEX_FLOAT, Format.COMPLEX_SHORT),
|
||||||
|
Rtl433Module(self.sampleRate, jsonOutput = not service),
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
class MultimonDemodulator(ServiceDemodulator, DialFrequencyReceiver):
|
class MultimonDemodulator(ServiceDemodulator, DialFrequencyReceiver):
|
||||||
|
|
@ -55,7 +78,7 @@ class PageDemodulator(MultimonDemodulator):
|
||||||
|
|
||||||
class EasDemodulator(MultimonDemodulator):
|
class EasDemodulator(MultimonDemodulator):
|
||||||
def __init__(self, service: bool = False):
|
def __init__(self, service: bool = False):
|
||||||
super().__init__(["EAS"], MultimonParser(service=service))
|
super().__init__(["EAS"], TextParser(service=service))
|
||||||
|
|
||||||
|
|
||||||
class SelCallDemodulator(MultimonDemodulator):
|
class SelCallDemodulator(MultimonDemodulator):
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
from pycsdr.types import Format
|
|
||||||
from csdr.module import PopenModule
|
|
||||||
|
|
||||||
|
|
||||||
class MultimonModule(PopenModule):
|
|
||||||
def __init__(self, decoders: list[str]):
|
|
||||||
self.decoders = decoders
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
def getCommand(self):
|
|
||||||
cmd = ["multimon-ng", "-", "-v0", "-c"]
|
|
||||||
for x in self.decoders:
|
|
||||||
cmd += ["-a", x]
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
def getInputFormat(self) -> Format:
|
|
||||||
return Format.SHORT
|
|
||||||
|
|
||||||
def getOutputFormat(self) -> Format:
|
|
||||||
return Format.CHAR
|
|
||||||
|
|
||||||
|
|
@ -8,10 +8,10 @@ class Rtl433Module(PopenModule):
|
||||||
self.jsonOutput = jsonOutput
|
self.jsonOutput = jsonOutput
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def getCommandTEST(self):
|
def getCommand(self):
|
||||||
return ["dummy433"]
|
return ["dummy433"]
|
||||||
|
|
||||||
def getCommand(self):
|
def getCommandOK(self):
|
||||||
return [
|
return [
|
||||||
"rtl_433", "-r", "cs16:-", "-s", str(self.sampleRate),
|
"rtl_433", "-r", "cs16:-", "-s", str(self.sampleRate),
|
||||||
"-M", "time:utc", "-F", "json" if self.jsonOutput else "kv",
|
"-M", "time:utc", "-F", "json" if self.jsonOutput else "kv",
|
||||||
|
|
@ -30,3 +30,21 @@ class Rtl433Module(PopenModule):
|
||||||
def getOutputFormat(self) -> Format:
|
def getOutputFormat(self) -> Format:
|
||||||
return Format.CHAR
|
return Format.CHAR
|
||||||
|
|
||||||
|
|
||||||
|
class MultimonModule(PopenModule):
|
||||||
|
def __init__(self, decoders: list[str]):
|
||||||
|
self.decoders = decoders
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def getCommand(self):
|
||||||
|
cmd = ["multimon-ng", "-", "-v0", "-c"]
|
||||||
|
for x in self.decoders:
|
||||||
|
cmd += ["-a", x]
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
def getInputFormat(self) -> Format:
|
||||||
|
return Format.SHORT
|
||||||
|
|
||||||
|
def getOutputFormat(self) -> Format:
|
||||||
|
return Format.CHAR
|
||||||
|
|
||||||
|
|
@ -285,7 +285,7 @@ PageMessagePanel.prototype.render = function() {
|
||||||
$(this.el).append($(
|
$(this.el).append($(
|
||||||
'<table>' +
|
'<table>' +
|
||||||
'<thead><tr>' +
|
'<thead><tr>' +
|
||||||
'<th>Paging</th>' +
|
'<th>Pager Messages</th>' +
|
||||||
'</tr></thead>' +
|
'</tr></thead>' +
|
||||||
'<tbody></tbody>' +
|
'<tbody></tbody>' +
|
||||||
'</table>'
|
'</table>'
|
||||||
|
|
@ -347,7 +347,7 @@ IsmMessagePanel.prototype.render = function() {
|
||||||
$(this.el).append($(
|
$(this.el).append($(
|
||||||
'<table>' +
|
'<table>' +
|
||||||
'<thead><tr>' +
|
'<thead><tr>' +
|
||||||
'<th>Devices</th>' +
|
'<th>Device Messages</th>' +
|
||||||
'</tr></thead>' +
|
'</tr></thead>' +
|
||||||
'<tbody></tbody>' +
|
'<tbody></tbody>' +
|
||||||
'</table>'
|
'</table>'
|
||||||
|
|
|
||||||
|
|
@ -617,10 +617,10 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient)
|
||||||
from csdr.chain.digiham import PocsagDemodulator
|
from csdr.chain.digiham import PocsagDemodulator
|
||||||
return PocsagDemodulator()
|
return PocsagDemodulator()
|
||||||
elif mod == "page":
|
elif mod == "page":
|
||||||
from csdr.chain.multimon import PageDemodulator
|
from csdr.chain.toolbox import PageDemodulator
|
||||||
return PageDemodulator()
|
return PageDemodulator()
|
||||||
elif mod == "selcall":
|
elif mod == "selcall":
|
||||||
from csdr.chain.multimon import SelCallDemodulator
|
from csdr.chain.toolbox import SelCallDemodulator
|
||||||
return SelCallDemodulator()
|
return SelCallDemodulator()
|
||||||
elif mod == "bpsk31":
|
elif mod == "bpsk31":
|
||||||
from csdr.chain.digimodes import PskDemodulator
|
from csdr.chain.digimodes import PskDemodulator
|
||||||
|
|
@ -644,8 +644,8 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient)
|
||||||
from csdr.chain.digimodes import FaxDemodulator
|
from csdr.chain.digimodes import FaxDemodulator
|
||||||
return FaxDemodulator()
|
return FaxDemodulator()
|
||||||
elif mod == "ism":
|
elif mod == "ism":
|
||||||
from csdr.chain.rtl433 import Rtl433Demodulator
|
from csdr.chain.toolbox import IsmDemodulator
|
||||||
return Rtl433Demodulator()
|
return IsmDemodulator()
|
||||||
|
|
||||||
def setSecondaryDemodulator(self, mod):
|
def setSecondaryDemodulator(self, mod):
|
||||||
demodulator = self._getSecondaryDemodulator(mod)
|
demodulator = self._getSecondaryDemodulator(mod)
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ class Modes(object):
|
||||||
# ),
|
# ),
|
||||||
DigitalMode(
|
DigitalMode(
|
||||||
"page",
|
"page",
|
||||||
"Paging",
|
"Page",
|
||||||
underlying=["nfm"],
|
underlying=["nfm"],
|
||||||
bandpass=Bandpass(-6000, 6000),
|
bandpass=Bandpass(-6000, 6000),
|
||||||
requirements=["page"],
|
requirements=["page"],
|
||||||
|
|
|
||||||
172
owrx/rtl433.py
172
owrx/rtl433.py
|
|
@ -1,172 +0,0 @@
|
||||||
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 json
|
|
||||||
|
|
||||||
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
|
|
||||||
self.colorBuf = {}
|
|
||||||
# Use these colors to mark devices by ID
|
|
||||||
self.colors = [
|
|
||||||
"#FFFFFF", "#999999", "#FF9999", "#FFCC99", "#FFFF99", "#CCFF99",
|
|
||||||
"#99FF99", "#99FFCC", "#99FFFF", "#99CCFF", "#9999FF", "#CC99FF",
|
|
||||||
"#FF99FF", "#FF99CC",
|
|
||||||
]
|
|
||||||
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 getColor(self, id: str) -> str:
|
|
||||||
if id in self.colorBuf:
|
|
||||||
# Sort entries in order of freshness
|
|
||||||
color = self.colorBuf.pop(id)
|
|
||||||
elif len(self.colorBuf) < len(self.colors):
|
|
||||||
# Assign each initial entry color based on its order
|
|
||||||
color = self.colors[len(self.colorBuf)]
|
|
||||||
else:
|
|
||||||
# If we run out of colors, reuse the oldest entry
|
|
||||||
color = self.colorBuf.pop(next(iter(self.colorBuf)))
|
|
||||||
# Done
|
|
||||||
self.colorBuf[id] = color
|
|
||||||
return color
|
|
||||||
|
|
||||||
def parse(self, msg: str):
|
|
||||||
# Expect JSON data in text form
|
|
||||||
out = json.loads(msg)
|
|
||||||
out.update({
|
|
||||||
"mode": "ISM",
|
|
||||||
"color": self.getColor(out["id"])
|
|
||||||
})
|
|
||||||
return out
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
@ -319,11 +319,11 @@ class ServiceHandler(SdrSourceEventClient):
|
||||||
from csdr.chain.digimodes import FaxDemodulator
|
from csdr.chain.digimodes import FaxDemodulator
|
||||||
return FaxDemodulator(service=True)
|
return FaxDemodulator(service=True)
|
||||||
elif mod == "page":
|
elif mod == "page":
|
||||||
from csdr.chain.multimon import PageDemodulator
|
from csdr.chain.toolbox import PageDemodulator
|
||||||
return PageDemodulator(service=True)
|
return PageDemodulator(service=True)
|
||||||
elif mod == "ism":
|
elif mod == "ism":
|
||||||
from csdr.chain.rtl433 import Rtl433Demodulator
|
from csdr.chain.toolbox import IsmDemodulator
|
||||||
return Rtl433Demodulator(service=True)
|
return IsmDemodulator(service=True)
|
||||||
|
|
||||||
raise ValueError("unsupported service modulation: {}".format(mod))
|
raise ValueError("unsupported service modulation: {}".format(mod))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,21 @@ from datetime import datetime
|
||||||
import pickle
|
import pickle
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class MultimonParser(ThreadModule):
|
|
||||||
def __init__(self, filePrefix: str = "MON", service: bool = False):
|
class TextParser(ThreadModule):
|
||||||
|
def __init__(self, filePrefix: str = "LOG", service: bool = False):
|
||||||
|
# Use these colors to label messages by address
|
||||||
|
self.colors = [
|
||||||
|
"#FFFFFF", "#999999", "#FF9999", "#FFCC99", "#FFFF99", "#CCFF99",
|
||||||
|
"#99FF99", "#99FFCC", "#99FFFF", "#99CCFF", "#9999FF", "#CC99FF",
|
||||||
|
"#FF99FF", "#FF99CC",
|
||||||
|
]
|
||||||
self.service = service
|
self.service = service
|
||||||
self.frequency = 0
|
self.frequency = 0
|
||||||
self.data = bytearray(b'')
|
self.data = bytearray(b'')
|
||||||
|
|
@ -20,6 +28,7 @@ class MultimonParser(ThreadModule):
|
||||||
self.file = None
|
self.file = None
|
||||||
self.maxLines = 10000
|
self.maxLines = 10000
|
||||||
self.cntLines = 0
|
self.cntLines = 0
|
||||||
|
self.colorBuf = {}
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
|
|
@ -77,12 +86,29 @@ class MultimonParser(ThreadModule):
|
||||||
def setDialFrequency(self, frequency: int) -> None:
|
def setDialFrequency(self, frequency: int) -> None:
|
||||||
self.frequency = frequency
|
self.frequency = frequency
|
||||||
|
|
||||||
|
# Compose name of this decoder, made of client/service and frequency
|
||||||
def myName(self):
|
def myName(self):
|
||||||
return "%s%s" % (
|
return "%s%s" % (
|
||||||
"Service" if self.service else "Client",
|
"Service" if self.service else "Client",
|
||||||
" at %dkHz" % (self.frequency // 1000) if self.frequency>0 else ""
|
" at %dkHz" % (self.frequency // 1000) if self.frequency>0 else ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Get a unique color for a given ID, reusing colors as we go
|
||||||
|
def getColor(self, id: str) -> str:
|
||||||
|
if id in self.colorBuf:
|
||||||
|
# Sort entries in order of freshness
|
||||||
|
color = self.colorBuf.pop(id)
|
||||||
|
elif len(self.colorBuf) < len(self.colors):
|
||||||
|
# Assign each initial entry color based on its order
|
||||||
|
color = self.colors[len(self.colorBuf)]
|
||||||
|
else:
|
||||||
|
# If we run out of colors, reuse the oldest entry
|
||||||
|
color = self.colorBuf.pop(next(iter(self.colorBuf)))
|
||||||
|
# Done
|
||||||
|
self.colorBuf[id] = color
|
||||||
|
return color
|
||||||
|
|
||||||
|
# DERIVED CLASSES SHOULD IMPLEMENT THIS FUNCTION!
|
||||||
def parse(self, msg: str):
|
def parse(self, msg: str):
|
||||||
# By default, do not parse, just return the string
|
# By default, do not parse, just return the string
|
||||||
return msg
|
return msg
|
||||||
|
|
@ -145,17 +171,26 @@ class MultimonParser(ThreadModule):
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
class PageParser(MultimonParser):
|
class IsmParser(TextParser):
|
||||||
|
def __init__(self, service: bool = False):
|
||||||
|
super().__init__(filePrefix="ISM", service=service)
|
||||||
|
|
||||||
|
def parse(self, msg: str):
|
||||||
|
# Expect JSON data in text form
|
||||||
|
out = json.loads(msg)
|
||||||
|
# Add mode name and a color to identify the sender
|
||||||
|
out.update({
|
||||||
|
"mode": "ISM",
|
||||||
|
"color": self.getColor(out["id"])
|
||||||
|
})
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class PageParser(TextParser):
|
||||||
def __init__(self, service: bool = False):
|
def __init__(self, service: bool = False):
|
||||||
# When true, try filtering out unreadable messages
|
# When true, try filtering out unreadable messages
|
||||||
pm = Config.get()
|
pm = Config.get()
|
||||||
self.filtering = "paging_filter" in pm and pm["paging_filter"]
|
self.filtering = "paging_filter" in pm and pm["paging_filter"]
|
||||||
# Use these colors to mark messages by address
|
|
||||||
self.colors = [
|
|
||||||
"#FFFFFF", "#999999", "#FF9999", "#FFCC99", "#FFFF99", "#CCFF99",
|
|
||||||
"#99FF99", "#99FFCC", "#99FFFF", "#99CCFF", "#9999FF", "#CC99FF",
|
|
||||||
"#FF99FF", "#FF99CC",
|
|
||||||
]
|
|
||||||
# POCSAG<baud>: Address: <num> Function: <hex> (Certainty: <num> )?(Numeric|Alpha|Skyper): <message>
|
# POCSAG<baud>: Address: <num> Function: <hex> (Certainty: <num> )?(Numeric|Alpha|Skyper): <message>
|
||||||
self.rePocsag = re.compile(r"POCSAG(\d+):\s*Address:\s*(\S+)\s+Function:\s*(\S+)(\s+Certainty:.*(\d+))?(\s+(\S+):\s*(.*))?")
|
self.rePocsag = re.compile(r"POCSAG(\d+):\s*Address:\s*(\S+)\s+Function:\s*(\S+)(\s+Certainty:.*(\d+))?(\s+(\S+):\s*(.*))?")
|
||||||
# FLEX|NNNN-NN-NN NN:NN:NN|<baud>/<value>/C/C|NN.NNN|NNNNNNNNN|<type>|<message>
|
# FLEX|NNNN-NN-NN NN:NN:NN|<baud>/<value>/C/C|NN.NNN|NNNNNNNNN|<type>|<message>
|
||||||
|
|
@ -170,8 +205,6 @@ class PageParser(MultimonParser):
|
||||||
self.reSpaces = re.compile(r"[\000-\037\s]+")
|
self.reSpaces = re.compile(r"[\000-\037\s]+")
|
||||||
# Fragmented messages will be assembled here
|
# Fragmented messages will be assembled here
|
||||||
self.flexBuf = {}
|
self.flexBuf = {}
|
||||||
# Color assignments will be maintained here
|
|
||||||
self.colorBuf = {}
|
|
||||||
# Construct parent object
|
# Construct parent object
|
||||||
super().__init__(filePrefix="PAGE", service=service)
|
super().__init__(filePrefix="PAGE", service=service)
|
||||||
|
|
||||||
|
|
@ -195,20 +228,6 @@ class PageParser(MultimonParser):
|
||||||
letters = len(msg) - spaces
|
letters = len(msg) - spaces
|
||||||
return (letters > 0) and (letters / (spaces+1) < 40)
|
return (letters > 0) and (letters / (spaces+1) < 40)
|
||||||
|
|
||||||
def getColor(self, capcode: str) -> str:
|
|
||||||
if capcode in self.colorBuf:
|
|
||||||
# Sort entries in order of freshness
|
|
||||||
color = self.colorBuf.pop(capcode)
|
|
||||||
elif len(self.colorBuf) < len(self.colors):
|
|
||||||
# Assign each initial entry color based on its order
|
|
||||||
color = self.colors[len(self.colorBuf)]
|
|
||||||
else:
|
|
||||||
# If we run out of colors, reuse the oldest entry
|
|
||||||
color = self.colorBuf.pop(next(iter(self.colorBuf)))
|
|
||||||
# Done
|
|
||||||
self.colorBuf[capcode] = color
|
|
||||||
return color
|
|
||||||
|
|
||||||
def parsePocsag(self, msg: str):
|
def parsePocsag(self, msg: str):
|
||||||
# No result yet
|
# No result yet
|
||||||
out = {}
|
out = {}
|
||||||
|
|
@ -305,12 +324,13 @@ class PageParser(MultimonParser):
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
class SelCallParser(MultimonParser):
|
class SelCallParser(TextParser):
|
||||||
def __init__(self, service: bool = False):
|
def __init__(self, service: bool = False):
|
||||||
self.reSplit = re.compile(r"(ZVEI1|ZVEI2|ZVEI3|DZVEI|PZVEI|DTMF|EEA|EIA|CCIR):\s+")
|
self.reSplit = re.compile(r"(ZVEI1|ZVEI2|ZVEI3|DZVEI|PZVEI|DTMF|EEA|EIA|CCIR):\s+")
|
||||||
self.reMatch = re.compile(r"ZVEI1|ZVEI2|ZVEI3|DZVEI|PZVEI|DTMF|EEA|EIA|CCIR")
|
self.reMatch = re.compile(r"ZVEI1|ZVEI2|ZVEI3|DZVEI|PZVEI|DTMF|EEA|EIA|CCIR")
|
||||||
self.mode = ""
|
self.mode = ""
|
||||||
super().__init__(service)
|
# Construct parent object
|
||||||
|
super().__init__(filePrefix="SELCALL", service=service)
|
||||||
|
|
||||||
def parse(self, msg: str):
|
def parse(self, msg: str):
|
||||||
# Parse SELCALL messages
|
# Parse SELCALL messages
|
||||||
Loading…
Reference in New Issue