restore pocsag functionality
This commit is contained in:
parent
b9f43654cd
commit
6014ce8921
@ -5,6 +5,8 @@ from owrx.aprs import Ax25Parser, AprsParser
|
||||
from pycsdr.modules import Convert, FmDemod
|
||||
from pycsdr.types import Format
|
||||
from owrx.aprs.module import DirewolfModule
|
||||
from digiham.modules import FskDemodulator, PocsagDecoder
|
||||
from owrx.pocsag import PocsagParser
|
||||
|
||||
|
||||
class AudioChopperDemodulator(SecondaryDemodulator, FixedAudioRateChain, DialFrequencyReceiver):
|
||||
@ -42,3 +44,20 @@ class PacketDemodulator(SecondaryDemodulator, FixedAudioRateChain, DialFrequency
|
||||
|
||||
def setDialFrequency(self, frequency: int) -> None:
|
||||
self.parser.setDialFrequency(frequency)
|
||||
|
||||
|
||||
class PocsagDemodulator(SecondaryDemodulator, FixedAudioRateChain):
|
||||
def __init__(self):
|
||||
workers = [
|
||||
FmDemod(),
|
||||
FskDemodulator(samplesPerSymbol=40, invert=True),
|
||||
PocsagDecoder(),
|
||||
PocsagParser(),
|
||||
]
|
||||
super().__init__(workers)
|
||||
|
||||
def supportsSquelch(self) -> bool:
|
||||
return False
|
||||
|
||||
def getFixedAudioRate(self) -> int:
|
||||
return 48000
|
||||
|
@ -2,6 +2,9 @@ from pycsdr.modules import Module as BaseModule
|
||||
from pycsdr.modules import Reader, Writer
|
||||
from pycsdr.types import Format
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from threading import Thread
|
||||
from io import BytesIO
|
||||
import pickle
|
||||
|
||||
|
||||
class Module(BaseModule, metaclass=ABCMeta):
|
||||
@ -23,3 +26,57 @@ class Module(BaseModule, metaclass=ABCMeta):
|
||||
@abstractmethod
|
||||
def getOutputFormat(self) -> Format:
|
||||
pass
|
||||
|
||||
|
||||
class ThreadModule(Module, Thread, metaclass=ABCMeta):
|
||||
def __init__(self):
|
||||
self.doRun = True
|
||||
super().__init__()
|
||||
Thread.__init__(self)
|
||||
|
||||
def _checkStart(self) -> None:
|
||||
if self.reader is not None and self.writer is not None:
|
||||
self.start()
|
||||
|
||||
def setReader(self, reader: Reader) -> None:
|
||||
super().setReader(reader)
|
||||
self._checkStart()
|
||||
|
||||
def setWriter(self, writer: Writer) -> None:
|
||||
super().setWriter(writer)
|
||||
self._checkStart()
|
||||
|
||||
@abstractmethod
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
self.doRun = False
|
||||
self.reader.stop()
|
||||
|
||||
|
||||
class PickleModule(ThreadModule):
|
||||
def getInputFormat(self) -> Format:
|
||||
return Format.CHAR
|
||||
|
||||
def getOutputFormat(self) -> Format:
|
||||
return Format.CHAR
|
||||
|
||||
def run(self):
|
||||
while self.doRun:
|
||||
data = self.reader.read()
|
||||
if data is None:
|
||||
self.doRun = False
|
||||
break
|
||||
io = BytesIO(data.tobytes())
|
||||
try:
|
||||
while True:
|
||||
output = self.process(pickle.load(io))
|
||||
if output is not None:
|
||||
self.writer.write(pickle.dumps(output))
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def process(self, input):
|
||||
pass
|
||||
|
@ -221,7 +221,7 @@ PacketMessagePanel.prototype.pushMessage = function(msg) {
|
||||
$.fn.packetMessagePanel = function() {
|
||||
if (!this.data('panel')) {
|
||||
this.data('panel', new PacketMessagePanel(this));
|
||||
};
|
||||
}
|
||||
return this.data('panel');
|
||||
};
|
||||
|
||||
@ -232,6 +232,10 @@ PocsagMessagePanel = function(el) {
|
||||
|
||||
PocsagMessagePanel.prototype = new MessagePanel();
|
||||
|
||||
PocsagMessagePanel.prototype.supportsMessage = function(message) {
|
||||
return message['mode'] === 'Pocsag';
|
||||
};
|
||||
|
||||
PocsagMessagePanel.prototype.render = function() {
|
||||
$(this.el).append($(
|
||||
'<table>' +
|
||||
|
@ -848,7 +848,8 @@ function on_ws_recv(evt) {
|
||||
var value = json['value'];
|
||||
var panels = [
|
||||
$("#openwebrx-panel-wsjt-message").wsjtMessagePanel(),
|
||||
$('#openwebrx-panel-packet-message').packetMessagePanel()
|
||||
$('#openwebrx-panel-packet-message').packetMessagePanel(),
|
||||
$('#openwebrx-panel-pocsag-message').pocsagMessagePanel()
|
||||
];
|
||||
if (!panels.some(function(panel) {
|
||||
if (!panel.supportsMessage(value)) return false;
|
||||
@ -861,9 +862,6 @@ function on_ws_recv(evt) {
|
||||
case 'log_message':
|
||||
divlog(json['value'], true);
|
||||
break;
|
||||
case 'pocsag_data':
|
||||
$('#openwebrx-panel-pocsag-message').pocsagMessagePanel().pushMessage(json['value']);
|
||||
break;
|
||||
case 'backoff':
|
||||
divlog("Server is currently busy: " + json['reason'], true);
|
||||
var $overlay = $('#openwebrx-error-overlay');
|
||||
|
@ -2,14 +2,9 @@ from owrx.map import Map, LatLngLocation
|
||||
from owrx.metrics import Metrics, CounterMetric
|
||||
from owrx.bands import Bandplan
|
||||
from datetime import datetime, timezone
|
||||
from csdr.module import Module
|
||||
from pycsdr.modules import Reader
|
||||
from pycsdr.types import Format
|
||||
from threading import Thread
|
||||
from io import BytesIO
|
||||
from csdr.module import PickleModule
|
||||
import re
|
||||
import logging
|
||||
import pickle
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -50,41 +45,8 @@ def getSymbolData(symbol, table):
|
||||
return {"symbol": symbol, "table": table, "index": ord(symbol) - 33, "tableindex": ord(table) - 33}
|
||||
|
||||
|
||||
class Ax25Parser(Module, Thread):
|
||||
def __init__(self):
|
||||
self.doRun = True
|
||||
super().__init__()
|
||||
|
||||
def getInputFormat(self) -> Format:
|
||||
return Format.CHAR
|
||||
|
||||
def getOutputFormat(self) -> Format:
|
||||
return Format.CHAR
|
||||
|
||||
def setReader(self, reader: Reader) -> None:
|
||||
super().setReader(reader)
|
||||
self.start()
|
||||
|
||||
def stop(self):
|
||||
self.doRun = False
|
||||
self.reader.stop()
|
||||
|
||||
def run(self):
|
||||
while self.doRun:
|
||||
data = self.reader.read()
|
||||
if data is None:
|
||||
self.doRun = False
|
||||
break
|
||||
io = BytesIO(data.tobytes())
|
||||
try:
|
||||
while True:
|
||||
frame = self.parse(pickle.load(io))
|
||||
if frame is not None:
|
||||
self.writer.write(pickle.dumps(frame))
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
def parse(self, ax25frame):
|
||||
class Ax25Parser(PickleModule):
|
||||
def process(self, ax25frame):
|
||||
control_pid = ax25frame.find(bytes([0x03, 0xF0]))
|
||||
if control_pid % 7 > 0:
|
||||
logger.warning("aprs packet framing error: control/pid position not aligned with 7-octet callsign data")
|
||||
@ -189,45 +151,15 @@ class AprsLocation(LatLngLocation):
|
||||
return res
|
||||
|
||||
|
||||
class AprsParser(Module, Thread):
|
||||
class AprsParser(PickleModule):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.metrics = {}
|
||||
self.doRun = True
|
||||
self.band = None
|
||||
|
||||
def setDialFrequency(self, freq):
|
||||
self.band = Bandplan.getSharedInstance().findBand(freq)
|
||||
|
||||
def setReader(self, reader: Reader) -> None:
|
||||
super().setReader(reader)
|
||||
self.start()
|
||||
|
||||
def getInputFormat(self) -> Format:
|
||||
return Format.CHAR
|
||||
|
||||
def getOutputFormat(self) -> Format:
|
||||
return Format.CHAR
|
||||
|
||||
def run(self):
|
||||
while self.doRun:
|
||||
data = self.reader.read()
|
||||
if data is None:
|
||||
self.doRun = False
|
||||
break
|
||||
io = BytesIO(data.tobytes())
|
||||
try:
|
||||
while True:
|
||||
frame = self.parse(pickle.load(io))
|
||||
if frame is not None:
|
||||
self.writer.write(pickle.dumps(frame))
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
self.doRun = False
|
||||
self.reader.stop()
|
||||
|
||||
def getMetric(self, category):
|
||||
if category not in self.metrics:
|
||||
band = "unknown"
|
||||
@ -250,7 +182,7 @@ class AprsParser(Module, Thread):
|
||||
return False
|
||||
return True
|
||||
|
||||
def parse(self, data):
|
||||
def process(self, data):
|
||||
try:
|
||||
# TODO how can we tell if this is an APRS frame at all?
|
||||
aprsData = self.parseAprsData(data)
|
||||
|
@ -1,9 +1,6 @@
|
||||
from pycsdr.modules import Reader
|
||||
from pycsdr.types import Format
|
||||
from csdr.module import Module
|
||||
from threading import Thread
|
||||
import socket
|
||||
import time
|
||||
from csdr.module import ThreadModule
|
||||
import pickle
|
||||
|
||||
import logging
|
||||
@ -16,11 +13,10 @@ TFEND = 0xDC
|
||||
TFESC = 0xDD
|
||||
|
||||
|
||||
class KissDeframer(Module, Thread):
|
||||
class KissDeframer(ThreadModule):
|
||||
def __init__(self):
|
||||
self.escaped = False
|
||||
self.buf = bytearray()
|
||||
self.doRun = True
|
||||
super().__init__()
|
||||
|
||||
def getInputFormat(self) -> Format:
|
||||
@ -29,10 +25,6 @@ class KissDeframer(Module, Thread):
|
||||
def getOutputFormat(self) -> Format:
|
||||
return Format.CHAR
|
||||
|
||||
def setReader(self, reader: Reader) -> None:
|
||||
super().setReader(reader)
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
while self.doRun:
|
||||
data = self.reader.read()
|
||||
@ -42,10 +34,6 @@ class KissDeframer(Module, Thread):
|
||||
for frame in self.parse(data):
|
||||
self.writer.write(pickle.dumps(frame))
|
||||
|
||||
def stop(self):
|
||||
self.doRun = False
|
||||
self.reader.stop()
|
||||
|
||||
def parse(self, input):
|
||||
for b in input:
|
||||
if b == FESC:
|
||||
|
@ -1,10 +1,9 @@
|
||||
from owrx.modes import Modes, AudioChopperMode
|
||||
from itertools import groupby
|
||||
import threading
|
||||
from owrx.audio import ProfileSourceSubscriber
|
||||
from owrx.audio.wav import AudioWriter
|
||||
from owrx.audio.queue import QueueJob
|
||||
from csdr.module import Module
|
||||
from csdr.module import ThreadModule
|
||||
from pycsdr.types import Format
|
||||
import pickle
|
||||
|
||||
@ -14,7 +13,7 @@ logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
class AudioChopper(threading.Thread, Module, ProfileSourceSubscriber):
|
||||
class AudioChopper(ThreadModule, ProfileSourceSubscriber):
|
||||
# TODO parser typing
|
||||
def __init__(self, mode_str: str, parser):
|
||||
self.parser = parser
|
||||
@ -26,7 +25,6 @@ class AudioChopper(threading.Thread, Module, ProfileSourceSubscriber):
|
||||
raise ValueError("Mode {} is not an audio chopper mode".format(mode_str))
|
||||
self.profile_source = mode.get_profile_source()
|
||||
super().__init__()
|
||||
Module.__init__(self)
|
||||
|
||||
def getInputFormat(self) -> Format:
|
||||
return Format.SHORT
|
||||
@ -49,14 +47,6 @@ class AudioChopper(threading.Thread, Module, ProfileSourceSubscriber):
|
||||
w.start()
|
||||
self.writers = writers
|
||||
|
||||
def setReader(self, reader):
|
||||
super().setReader(reader)
|
||||
self.start()
|
||||
|
||||
def stop(self):
|
||||
self.reader.stop()
|
||||
super().stop()
|
||||
|
||||
def run(self) -> None:
|
||||
logger.debug("Audio chopper starting up")
|
||||
self.setup_writers()
|
||||
|
@ -434,9 +434,6 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient):
|
||||
def write_sdr_error(self, message):
|
||||
self.send({"type": "sdr_error", "value": message})
|
||||
|
||||
def write_pocsag_data(self, data):
|
||||
self.send({"type": "pocsag_data", "value": data})
|
||||
|
||||
def write_backoff_message(self, reason):
|
||||
self.send({"type": "backoff", "reason": reason})
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
from owrx.meta import MetaParser
|
||||
from owrx.wsjt import WsjtParser
|
||||
from owrx.js8 import Js8Parser
|
||||
from owrx.aprs import AprsParser
|
||||
from owrx.pocsag import PocsagParser
|
||||
from owrx.source import SdrSourceEventClient, SdrSourceState, SdrClientClass
|
||||
from owrx.property import PropertyStack, PropertyLayer, PropertyValidator
|
||||
from owrx.property.validators import OrValidator, RegexValidator, BoolValidator
|
||||
@ -15,7 +13,7 @@ from csdr.chain.clientaudio import ClientAudioChain
|
||||
from csdr.chain.analog import NFm, WFm, Am, Ssb
|
||||
from csdr.chain.digiham import DigihamChain, Dmr, Dstar, Nxdn, Ysf
|
||||
from csdr.chain.fft import FftChain
|
||||
from csdr.chain.digimodes import AudioChopperDemodulator, PacketDemodulator
|
||||
from csdr.chain.digimodes import AudioChopperDemodulator, PacketDemodulator, PocsagDemodulator
|
||||
from pycsdr.modules import Buffer, Writer
|
||||
from pycsdr.types import Format
|
||||
from typing import Union
|
||||
@ -283,7 +281,6 @@ class DspManager(Output, SdrSourceEventClient):
|
||||
self.sdrSource = sdrSource
|
||||
self.parsers = {
|
||||
"meta": MetaParser(self.handler),
|
||||
"pocsag_demod": PocsagParser(self.handler),
|
||||
"js8_demod": Js8Parser(self.handler),
|
||||
}
|
||||
|
||||
@ -494,6 +491,8 @@ class DspManager(Output, SdrSourceEventClient):
|
||||
return AudioChopperDemodulator(mod, WsjtParser())
|
||||
elif mod == "packet":
|
||||
return PacketDemodulator()
|
||||
elif mod == "pocsag":
|
||||
return PocsagDemodulator()
|
||||
return None
|
||||
|
||||
def setSecondaryDemodulator(self, mod):
|
||||
|
@ -1,17 +1,38 @@
|
||||
from owrx.parser import Parser
|
||||
from csdr.module import ThreadModule
|
||||
from pycsdr.types import Format
|
||||
import pickle
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PocsagParser(Parser):
|
||||
class PocsagParser(ThreadModule):
|
||||
def getInputFormat(self) -> Format:
|
||||
return Format.CHAR
|
||||
|
||||
def getOutputFormat(self) -> Format:
|
||||
return Format.CHAR
|
||||
|
||||
def run(self):
|
||||
while self.doRun:
|
||||
data = self.reader.read()
|
||||
if data is None:
|
||||
self.doRun = False
|
||||
else:
|
||||
for frame in self.parse(data.tobytes()):
|
||||
self.writer.write(pickle.dumps(frame))
|
||||
|
||||
def parse(self, raw):
|
||||
try:
|
||||
fields = raw.decode("ascii", "replace").rstrip("\n").split(";")
|
||||
meta = {v[0]: "".join(v[1:]) for v in map(lambda x: x.split(":"), fields) if v[0] != ""}
|
||||
if "address" in meta:
|
||||
meta["address"] = int(meta["address"])
|
||||
self.handler.write_pocsag_data(meta)
|
||||
except Exception:
|
||||
logger.exception("Exception while parsing Pocsag message")
|
||||
for line in raw.split(b"\n"):
|
||||
if not len(line):
|
||||
continue
|
||||
try:
|
||||
fields = line.decode("ascii", "replace").split(";")
|
||||
meta = {v[0]: "".join(v[1:]) for v in map(lambda x: x.split(":"), fields) if v[0] != ""}
|
||||
if "address" in meta:
|
||||
meta["address"] = int(meta["address"])
|
||||
meta["mode"] = "Pocsag"
|
||||
yield meta
|
||||
except Exception:
|
||||
logger.exception("Exception while parsing Pocsag message")
|
||||
|
Loading…
Reference in New Issue
Block a user