add a tap for image data into MQTT

This commit is contained in:
Jakob Ketterl 2024-02-16 22:47:25 +01:00
parent f2532842b5
commit b26960e1ab
4 changed files with 125 additions and 21 deletions

View file

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

View file

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

View file

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