restore pocsag functionality

This commit is contained in:
Jakob Ketterl 2021-09-06 20:00:14 +02:00
parent b9f43654cd
commit 6014ce8921
10 changed files with 126 additions and 121 deletions

View File

@ -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

View File

@ -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

View File

@ -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>' +

View File

@ -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');

View File

@ -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)

View File

@ -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:

View File

@ -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()

View File

@ -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})

View File

@ -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):

View File

@ -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")