forked from viewit/KX-Bridge-Release
fix: notification images
This commit is contained in:
@@ -844,6 +844,7 @@ class KobraXBridge:
|
||||
self._camera_autostarted: bool = False
|
||||
self._camera_user_stopped: bool = False # User hat Kamera während Druck manuell gestoppt
|
||||
self.camera_cache: CameraCache = CameraCache()
|
||||
self._loop: "asyncio.AbstractEventLoop | None" = None
|
||||
self._prev_kobra_state: str = ""
|
||||
self._print_active: bool = False
|
||||
self._notify_every_minutes: int = 0
|
||||
@@ -1108,6 +1109,61 @@ class KobraXBridge:
|
||||
# Notifications (apprise)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
async def _capture_notification_image(self, timeout: float = 15.0) -> bytes:
|
||||
"""Liefert einen aktuellen JPEG-Frame für Bild-Benachrichtigungen.
|
||||
|
||||
Falls die Kamera nicht aktiv streamt, wird sie kurz eingeschaltet, ein
|
||||
Frame abgegriffen und – sofern wir sie selbst gestartet haben – wieder
|
||||
ausgeschaltet (toggle on → snapshot → toggle off).
|
||||
"""
|
||||
loop = asyncio.get_event_loop()
|
||||
# 1) Bereits ein frischer Frame im Cache (z. B. WebUI schaut zu)? → direkt nutzen.
|
||||
if self.camera_cache.latest_jpeg and (time.time() - self.camera_cache.latest_jpeg_ts) < 3.0:
|
||||
return self.camera_cache.latest_jpeg
|
||||
|
||||
started_here = False
|
||||
try:
|
||||
# 2) Kamera am Drucker einschalten, falls sie nicht ohnehin läuft.
|
||||
if not self._camera_autostarted:
|
||||
try:
|
||||
await loop.run_in_executor(None, self.client.start_camera)
|
||||
started_here = True
|
||||
log.debug("Kamera für Bild-Benachrichtigung temporär gestartet")
|
||||
except Exception as e:
|
||||
log.warning(f"Kamera-Start für Benachrichtigung fehlgeschlagen: {e}")
|
||||
# rtspUrl aktualisieren (kommt im info/report mit, wenn Kamera streamt)
|
||||
try:
|
||||
info = await loop.run_in_executor(None, self.client.query_info)
|
||||
if info:
|
||||
self._on_info(info)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
url = self._state.get("camera_url", "")
|
||||
if not url:
|
||||
return b""
|
||||
self.camera_cache.set_url(url)
|
||||
await self.camera_cache.ensure_running()
|
||||
|
||||
# 3) Auf einen frischen Frame warten (ffmpeg + RTSP-Warmup).
|
||||
ref_ts = time.time()
|
||||
deadline = ref_ts + timeout
|
||||
while time.time() < deadline:
|
||||
if self.camera_cache.latest_jpeg and self.camera_cache.latest_jpeg_ts >= ref_ts:
|
||||
return self.camera_cache.latest_jpeg
|
||||
await asyncio.sleep(0.3)
|
||||
# Kein frischer Frame – notfalls den letzten vorhandenen nehmen.
|
||||
return self.camera_cache.latest_jpeg
|
||||
finally:
|
||||
# 4) Wieder ausschalten, wenn wir sie nur für den Snapshot gestartet
|
||||
# haben (nicht während eines laufenden Drucks mit Auto-Kamera).
|
||||
if started_here and not self._camera_autostarted:
|
||||
try:
|
||||
await loop.run_in_executor(None, self.client.stop_camera)
|
||||
log.debug("Kamera nach Bild-Benachrichtigung wieder gestoppt")
|
||||
except Exception as e:
|
||||
log.debug(f"Kamera-Stop nach Benachrichtigung fehlgeschlagen: {e}")
|
||||
|
||||
def _notify(self, event: str, filename: str = ""):
|
||||
if self._notifications_paused:
|
||||
return
|
||||
@@ -1161,13 +1217,17 @@ class KobraXBridge:
|
||||
ap2.add(u)
|
||||
attach = None
|
||||
tmppath = None
|
||||
jpeg = self.camera_cache.latest_jpeg
|
||||
if not jpeg and self.camera_cache._url:
|
||||
# Camera URL is set but no frame yet — wait briefly (camera may be starting)
|
||||
deadline = time.monotonic() + 5.0
|
||||
while not jpeg and time.monotonic() < deadline:
|
||||
time.sleep(0.3)
|
||||
jpeg = b""
|
||||
if self._loop is not None:
|
||||
try:
|
||||
fut = asyncio.run_coroutine_threadsafe(
|
||||
self._capture_notification_image(timeout=15.0), self._loop)
|
||||
jpeg = fut.result(timeout=30.0) or b""
|
||||
except Exception as e:
|
||||
log.warning(f"Kamera-Snapshot für Benachrichtigung fehlgeschlagen: {e}")
|
||||
jpeg = self.camera_cache.latest_jpeg
|
||||
else:
|
||||
jpeg = self.camera_cache.latest_jpeg
|
||||
if jpeg:
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as f:
|
||||
@@ -5494,6 +5554,7 @@ async def run_bridge(args):
|
||||
client, args=per_args, store=store,
|
||||
printer_id=pid, all_bridges=all_bridges,
|
||||
)
|
||||
bridge._loop = loop
|
||||
# printer_name aus config.ini übernehmen falls gesetzt
|
||||
if p.get("name"):
|
||||
bridge._state["printer_name"] = p["name"]
|
||||
|
||||
Reference in New Issue
Block a user