mirror of
https://github.com/jketterl/openwebrx.git
synced 2026-02-03 14:24:56 +01:00
add a tap for image data into MQTT
This commit is contained in:
parent
f2532842b5
commit
b26960e1ab
|
|
@ -1,16 +1,38 @@
|
|||
from owrx.sstv import SstvParser
|
||||
from csdr.chain.demodulator import SecondaryDemodulator, FixedAudioRateChain
|
||||
from pycsdr.modules import FmDemod
|
||||
from pycsdr.modules import FmDemod, Buffer
|
||||
from pycsdr.types import Format
|
||||
from csdrsstv.modules import SstvDecoder
|
||||
from owrx.feature import FeatureDetector
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Sstv(SecondaryDemodulator, FixedAudioRateChain):
|
||||
def __init__(self):
|
||||
self.imageBuffer = Buffer(Format.CHAR)
|
||||
super().__init__([
|
||||
FmDemod(),
|
||||
SstvDecoder(),
|
||||
SstvParser(),
|
||||
])
|
||||
self.pngAdapter = None
|
||||
# tap into the pipeline to be able to send images off to MQTT
|
||||
if FeatureDetector().is_available("png"):
|
||||
# local import due to optional features
|
||||
from owrx.sstv.png import PngAdapter
|
||||
self.pngAdapter = PngAdapter()
|
||||
self.pngAdapter.setReader(self.imageBuffer.getReader())
|
||||
|
||||
def _connect(self, w1, w2, buffer: Optional[Buffer] = None) -> None:
|
||||
if isinstance(w1, SstvDecoder):
|
||||
buffer = self.imageBuffer
|
||||
super()._connect(w1, w2, buffer)
|
||||
|
||||
def stop(self):
|
||||
if self.pngAdapter is not None:
|
||||
self.pngAdapter.stop()
|
||||
self.pngAdapter = None
|
||||
super().stop()
|
||||
|
||||
def getFixedAudioRate(self) -> int:
|
||||
return 12000
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ class FeatureDetector(object):
|
|||
"dab": ["csdreti", "dablin"],
|
||||
"mqtt": ["paho_mqtt"],
|
||||
"sstv": ["csdrsstv"],
|
||||
"png": ["pypng"],
|
||||
}
|
||||
|
||||
def feature_availability(self):
|
||||
|
|
@ -706,3 +707,13 @@ class FeatureDetector(object):
|
|||
)
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def has_pypng(self):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
try:
|
||||
import png
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -2,16 +2,15 @@ from pycsdr.types import Format
|
|||
from csdr.module import ThreadModule
|
||||
import pickle
|
||||
import struct
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from typing import List
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SstvParser(ThreadModule):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
class ImageParser(ThreadModule, metaclass=ABCMeta):
|
||||
def run(self):
|
||||
stash = bytes()
|
||||
lines = 0
|
||||
|
|
@ -28,34 +27,58 @@ class SstvParser(ThreadModule):
|
|||
if synced:
|
||||
(vis, pixels, lines) = struct.unpack("hhh", stash[4:10])
|
||||
stash = stash[10:]
|
||||
logger.debug("got image data: VIS = %i resolution: %i x %i", vis, pixels, lines)
|
||||
message = {
|
||||
"mode": "SSTV",
|
||||
"vis": vis,
|
||||
"resolution": {
|
||||
"width": pixels,
|
||||
"height": lines
|
||||
}
|
||||
}
|
||||
self.writer.write(pickle.dumps(message))
|
||||
self.startImage(vis, pixels, lines)
|
||||
else:
|
||||
logger.debug("search for sync...")
|
||||
# go search for sync... byte by byte.
|
||||
stash = stash[1:]
|
||||
while synced and len(stash) >= pixels * 3:
|
||||
line = [x for x in stash[:pixels * 3]]
|
||||
stash = stash[pixels * 3:]
|
||||
message = {
|
||||
"mode": "SSTV",
|
||||
"line": line,
|
||||
}
|
||||
self.writer.write(pickle.dumps(message))
|
||||
self.processLine(line)
|
||||
lines -= 1
|
||||
if lines == 0:
|
||||
self.finishImage()
|
||||
synced = False
|
||||
|
||||
def getInputFormat(self) -> Format:
|
||||
return Format.CHAR
|
||||
|
||||
@abstractmethod
|
||||
def startImage(self, vis: int, pixels: int, lines: int) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def processLine(self, line: List[int]) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def finishImage(self):
|
||||
pass
|
||||
|
||||
|
||||
class SstvParser(ImageParser):
|
||||
def getOutputFormat(self) -> Format:
|
||||
return Format.CHAR
|
||||
|
||||
def startImage(self, vis: int, pixels: int, lines: int) -> None:
|
||||
logger.debug("got image data: VIS = %i resolution: %i x %i", vis, pixels, lines)
|
||||
message = {
|
||||
"mode": "SSTV",
|
||||
"vis": vis,
|
||||
"resolution": {
|
||||
"width": pixels,
|
||||
"height": lines
|
||||
}
|
||||
}
|
||||
self.writer.write(pickle.dumps(message))
|
||||
|
||||
def processLine(self, line: List[int]) -> None:
|
||||
message = {
|
||||
"mode": "SSTV",
|
||||
"line": line,
|
||||
}
|
||||
self.writer.write(pickle.dumps(message))
|
||||
|
||||
def finishImage(self):
|
||||
pass
|
||||
|
||||
48
owrx/sstv/png.py
Normal file
48
owrx/sstv/png.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
from pycsdr.types import Format
|
||||
from owrx.sstv import ImageParser
|
||||
from typing import List
|
||||
from io import BytesIO
|
||||
from owrx.reporting import ReportingEngine
|
||||
import png
|
||||
import base64
|
||||
|
||||
|
||||
class PngAdapter(ImageParser):
|
||||
def __init__(self):
|
||||
self.vis = 0
|
||||
self.pixels = 0
|
||||
self.lines = 0
|
||||
self.image_data = []
|
||||
super().__init__()
|
||||
|
||||
def getOutputFormat(self) -> Format:
|
||||
return Format.CHAR
|
||||
|
||||
def startImage(self, vis: int, pixels: int, lines: int) -> None:
|
||||
self.image_data = []
|
||||
self.vis = vis
|
||||
self.pixels = pixels
|
||||
self.lines = lines
|
||||
|
||||
def processLine(self, line: List[int]) -> None:
|
||||
self.image_data += [line]
|
||||
|
||||
def finishImage(self):
|
||||
f = BytesIO()
|
||||
writer = png.Writer(self.pixels, self.lines, greyscale=False)
|
||||
writer.write(f, self.image_data)
|
||||
image_binary = bytes(f.getbuffer())
|
||||
if self.writer is not None:
|
||||
self.writer.write(image_binary)
|
||||
spot = {
|
||||
"mode": "SSTV",
|
||||
"vis": self.vis,
|
||||
"image": base64.b64encode(image_binary).decode('ascii'),
|
||||
}
|
||||
ReportingEngine.getSharedInstance().spot(spot)
|
||||
f.close()
|
||||
|
||||
def _checkStart(self) -> None:
|
||||
# we don't need a writer
|
||||
if self.reader is not None:
|
||||
self.start()
|
||||
Loading…
Reference in a new issue