Finally implemented SelCall parsing properly.
This commit is contained in:
parent
0495fefcec
commit
7c182ed569
|
|
@ -2,13 +2,13 @@ from csdr.chain.demodulator import ServiceDemodulator, DialFrequencyReceiver
|
|||
from csdr.module.multimon import MultimonModule
|
||||
from pycsdr.modules import FmDemod, AudioResampler, Convert
|
||||
from pycsdr.types import Format
|
||||
from owrx.multimon import MultimonParser
|
||||
from owrx.multimon import MultimonParser, FlexParser, SelCallParser
|
||||
|
||||
|
||||
class MultimonDemodulator(ServiceDemodulator, DialFrequencyReceiver):
|
||||
def __init__(self, decoders: list[str], service: bool = False):
|
||||
def __init__(self, decoders: list[str], parser):
|
||||
self.sampleRate = 24000
|
||||
self.parser = MultimonParser(service=service)
|
||||
self.parser = parser
|
||||
workers = [
|
||||
FmDemod(),
|
||||
AudioResampler(self.sampleRate, 22050),
|
||||
|
|
@ -30,24 +30,28 @@ class MultimonDemodulator(ServiceDemodulator, DialFrequencyReceiver):
|
|||
|
||||
class FlexDemodulator(MultimonDemodulator):
|
||||
def __init__(self, service: bool = False):
|
||||
super().__init__(["FLEX"], service=service)
|
||||
super().__init__(["FLEX"], FlexParser(service=service))
|
||||
|
||||
|
||||
class PocsageDemodulator(MultimonDemodulator):
|
||||
def __init__(self, service: bool = False):
|
||||
super().__init__(["POCSAG512", "POCSAG1200", "POCSAG2400"], service=service)
|
||||
super().__init__(
|
||||
["POCSAG512", "POCSAG1200", "POCSAG2400"],
|
||||
MultimonParser(service=service)
|
||||
)
|
||||
|
||||
|
||||
class EasDemodulator(MultimonDemodulator):
|
||||
def __init__(self, service: bool = False):
|
||||
super().__init__(["EAS"], service=service)
|
||||
super().__init__(["EAS"], MultimonParser(service=service))
|
||||
|
||||
|
||||
class SelCallDemodulator(MultimonDemodulator):
|
||||
def __init__(self, service: bool = False):
|
||||
super().__init__([
|
||||
super().__init__(
|
||||
# These aappear to be rarely used and very similar, so they trigger at once
|
||||
# "ZVEI1", "ZVEI2", "ZVEI3", "DZVEI", "PZVEI",
|
||||
"DTMF", "EEA", "EIA", "CCIR"
|
||||
], service=service)
|
||||
["DTMF", "EEA", "EIA", "CCIR"],
|
||||
SelCallParser(service=service)
|
||||
)
|
||||
|
||||
|
|
|
|||
181
owrx/multimon.py
181
owrx/multimon.py
|
|
@ -12,22 +12,10 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
class MultimonParser(ThreadModule):
|
||||
def __init__(self, service: bool = False):
|
||||
# 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 NNNNNNNNN|<type>|<message>"
|
||||
self.reFlex1 = re.compile(r"FLEX\|(\d\d\d\d-\d\d-\d\d\s+\d\d:\d\d:\d\d)\|(\d+/\d+/\S/\S)\|(\d\d\.\d\d\d)\|(\d+(?:\s+\d+)?)\|(\S+)\|(.*)")
|
||||
# FLEX: NNNN-NN-NN NN:NN:NN <baud>/<value>/C NN.NNN [NNNNNNNNN] <type> <message>
|
||||
self.reFlex2 = re.compile(r"FLEX:\s+(\d\d\d\d-\d\d-\d\d\s+\d\d:\d\d:\d\d)\s+(\d+/\d+/\S)\s+(\d\d\.\d\d\d)\s+\[(\d+)\]\s+(\S+)\s+(.*)")
|
||||
# FLEX message status
|
||||
self.reFlex3 = re.compile(r"\d+/\d+/(\S)/\S")
|
||||
# <mode>: C
|
||||
self.reSelCall = re.compile(r"(ZVEI1|ZVEI2|ZVEI3|DZVEI|PZVEI|DTMF|EEA|EIA|CCIR):\s+([0-9A-F]+?)")
|
||||
|
||||
self.service = service
|
||||
self.frequency = 0
|
||||
self.data = bytearray(b'')
|
||||
self.file = None
|
||||
self.selMode = ""
|
||||
self.flexBuf = {}
|
||||
super().__init__()
|
||||
|
||||
def __del__(self):
|
||||
|
|
@ -81,6 +69,10 @@ class MultimonParser(ThreadModule):
|
|||
" at %dkHz" % (self.frequency // 1000) if self.frequency>0 else ""
|
||||
)
|
||||
|
||||
def parse(self, msg: str):
|
||||
# By default, fail parsing the message
|
||||
return {}
|
||||
|
||||
def run(self):
|
||||
logger.debug("%s starting..." % self.myName())
|
||||
# Run while there is input data
|
||||
|
|
@ -126,77 +118,8 @@ class MultimonParser(ThreadModule):
|
|||
# Empty result
|
||||
out = {}
|
||||
else:
|
||||
# Parse FLEX and SELCALL messages
|
||||
rf = self.reFlex1.match(msg)
|
||||
rf = self.reFlex2.match(msg) if not rf else rf
|
||||
rs = self.reSelCall.findall(msg) if not rf else []
|
||||
|
||||
#
|
||||
# FLEX
|
||||
#
|
||||
if rf is not None:
|
||||
tstamp = rf.group(1)
|
||||
state = rf.group(2)
|
||||
frame = rf.group(3)
|
||||
capcode = rf.group(4)
|
||||
msgtype = rf.group(5)
|
||||
msg = rf.group(6)
|
||||
rm = self.reFlex3.match(state)
|
||||
frag = "" if not rm else rm.group(1)
|
||||
# Assemble fragmented messages in flexBuf
|
||||
if frag == "F" or frag == "C":
|
||||
# Do not let flexBuf grow too much
|
||||
if len(self.flexBuf)>1024:
|
||||
self.flexBuf = {}
|
||||
# Accumulate messages in flexBuf, index by capcode
|
||||
if capcode in self.flexBuf:
|
||||
self.flexBuf[capcode] += msg
|
||||
else:
|
||||
self.flexBuf[capcode] = msg
|
||||
# Only output message once it completes
|
||||
if frag == "F":
|
||||
msg = ""
|
||||
elif frag == "C":
|
||||
msg = self.flexBuf[capcode]
|
||||
del self.flexBuf[capcode]
|
||||
# Do not report fragments of messages
|
||||
if frag == "F":
|
||||
out = {}
|
||||
else:
|
||||
out = {
|
||||
"mode": "FLEX",
|
||||
"timestamp": tstamp,
|
||||
"state": state,
|
||||
"frame": frame,
|
||||
"capcode": capcode,
|
||||
"type": msgtype
|
||||
}
|
||||
# Output message adding hash for numeric messages
|
||||
if len(msg)>0:
|
||||
if msgtype != "ALN":
|
||||
msg = "# " + msg
|
||||
out.update({ "message": msg })
|
||||
|
||||
#
|
||||
# SELCALL
|
||||
#
|
||||
elif len(rs)>0:
|
||||
# Just output characters as they are, add SELCALL
|
||||
# standard name when changing standard
|
||||
out = ""
|
||||
for x in rs:
|
||||
if x[0] == self.selMode:
|
||||
out += x[1]
|
||||
else:
|
||||
self.selMode = x[0]
|
||||
out += " [%s] %s" % (x[0], x[1])
|
||||
|
||||
#
|
||||
# Everything else
|
||||
#
|
||||
else:
|
||||
# Failed to parse this message
|
||||
out = {}
|
||||
# Let parse() function do its thing
|
||||
out = self.parse(msg)
|
||||
|
||||
except Exception as exptn:
|
||||
logger.debug("%s: Exception parsing: %s" % (self.myName(), str(exptn)))
|
||||
|
|
@ -206,3 +129,95 @@ class MultimonParser(ThreadModule):
|
|||
|
||||
# Return parsed result or None if no result yet
|
||||
return out
|
||||
|
||||
|
||||
class FlexParser(MultimonParser):
|
||||
def __init__(self, service: bool = False):
|
||||
# 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 NNNNNNNNN|<type>|<message>"
|
||||
self.reFlex1 = re.compile(r"FLEX\|(\d\d\d\d-\d\d-\d\d\s+\d\d:\d\d:\d\d)\|(\d+/\d+/\S/\S)\|(\d\d\.\d\d\d)\|(\d+(?:\s+\d+)?)\|(\S+)\|(.*)")
|
||||
# FLEX: NNNN-NN-NN NN:NN:NN <baud>/<value>/C NN.NNN [NNNNNNNNN] <type> <message>
|
||||
self.reFlex2 = re.compile(r"FLEX:\s+(\d\d\d\d-\d\d-\d\d\s+\d\d:\d\d:\d\d)\s+(\d+/\d+/\S)\s+(\d\d\.\d\d\d)\s+\[(\d+)\]\s+(\S+)\s+(.*)")
|
||||
# FLEX message status
|
||||
self.reFlex3 = re.compile(r"\d+/\d+/(\S)/\S")
|
||||
# Fragmented messages will be assembled here
|
||||
self.flexBuf = {}
|
||||
# Construct parent object
|
||||
super().__init__(service)
|
||||
|
||||
def parse(self, msg: str):
|
||||
# Parse FLEX messages
|
||||
r = self.reFlex1.match(msg)
|
||||
r = self.reFlex2.match(msg) if not r else r
|
||||
out = {}
|
||||
|
||||
if r is not None:
|
||||
tstamp = r.group(1)
|
||||
state = r.group(2)
|
||||
frame = r.group(3)
|
||||
capcode = r.group(4)
|
||||
msgtype = r.group(5)
|
||||
msg = r.group(6)
|
||||
rm = self.reFlex3.match(state)
|
||||
frag = "" if not rm else rm.group(1)
|
||||
# Assemble fragmented messages in flexBuf
|
||||
if frag == "F" or frag == "C":
|
||||
# Do not let flexBuf grow too much
|
||||
if len(self.flexBuf)>1024:
|
||||
self.flexBuf = {}
|
||||
# Accumulate messages in flexBuf, index by capcode
|
||||
if capcode in self.flexBuf:
|
||||
self.flexBuf[capcode] += msg
|
||||
else:
|
||||
self.flexBuf[capcode] = msg
|
||||
# Only output message once it completes
|
||||
if frag == "F":
|
||||
msg = ""
|
||||
elif frag == "C":
|
||||
msg = self.flexBuf[capcode]
|
||||
del self.flexBuf[capcode]
|
||||
# Do not report fragments of messages
|
||||
if frag != "F":
|
||||
out.update({
|
||||
"mode": "FLEX",
|
||||
"timestamp": tstamp,
|
||||
"state": state,
|
||||
"frame": frame,
|
||||
"capcode": capcode,
|
||||
"type": msgtype
|
||||
})
|
||||
# Output message adding hash for numeric messages
|
||||
if len(msg)>0:
|
||||
if msgtype != "ALN":
|
||||
msg = "# " + msg
|
||||
out.update({ "message": msg })
|
||||
|
||||
# Done
|
||||
return out
|
||||
|
||||
|
||||
class SelCallParser(MultimonParser):
|
||||
def __init__(self, service: bool = False):
|
||||
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.mode = ""
|
||||
super().__init__(service)
|
||||
|
||||
def parse(self, msg: str):
|
||||
# Parse SELCALL messages
|
||||
dec = None
|
||||
out = ""
|
||||
r = self.reSplit.split(msg)
|
||||
|
||||
for s in r:
|
||||
if self.reMatch.match(s):
|
||||
dec = s
|
||||
elif dec is not None and len(s)>0:
|
||||
if dec != self.mode:
|
||||
out += " [" + dec + "]"
|
||||
self.mode = dec
|
||||
out += " " + s
|
||||
dec = None
|
||||
# Done
|
||||
return out
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue