From b5b56e4c1854314fb519d13af9dda80a10ee0b8a Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Tue, 10 Nov 2026 10:46:04 +0100 Subject: feat(k1c): live camera stream instead of snapshots The camera was a snapshot reloaded on every poll. It is now the webcam's MJPEG stream_url, which an plays by itself at the camera's own rate (15 fps on the K1C). The stream connects only while the K1C tab is on screen and the page visible, and disconnects otherwise, so a hidden tab does not keep the printer streaming. A dropped stream hides the image and reconnects on the next poll. The image is shown as soon as its source is set, since a never-ending stream may never fire load. Co-Authored-By: Claude Opus 5.5 --- deckit/modules/k1c.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'deckit') diff --git a/deckit/modules/k1c.py b/deckit/modules/k1c.py index 8481e91..1f89088 100644 --- a/deckit/modules/k1c.py +++ b/deckit/modules/k1c.py @@ -41,10 +41,10 @@ def _http(url, data=None, timeout=2): return json.loads(r.read()) -def _snapshot(webcams) -> str | None: - """The first enabled webcam's snapshot URL, if it is plain http(s).""" +def _camera(webcams) -> str | None: + """The first enabled webcam's live MJPEG stream URL, if it is plain http(s).""" for cam in webcams or []: - url = cam.get("snapshot_url") or "" + url = cam.get("stream_url") or "" if cam.get("enabled", True) and url.startswith(("http://", "https://")): return url return None @@ -64,7 +64,7 @@ def _first(*values): return next((v for v in values if v is not None), None) -def summarize(status: dict, snapshot: str | None = None) -> dict: +def summarize(status: dict, camera: str | None = None) -> dict: """The panel's state from Moonraker's object status.""" ps = status.get("print_stats") or {} sd = status.get("virtual_sdcard") or {} @@ -99,14 +99,14 @@ def summarize(status: dict, snapshot: str | None = None) -> dict: (status.get("output_pin fan0") or {}).get("value")), "position": ((status.get("toolhead") or {}).get("position") or [None] * 3)[:3], "speed_factor": (status.get("gcode_move") or {}).get("speed_factor"), - "snapshot": snapshot, + "camera": camera, } def make(cfg: dict) -> Module: base = str(cfg.get("k1c", {}).get("url", "")).rstrip("/") http = cfg.get("http", _http) - down = {"until": 0.0, "snapshot": None} + down = {"until": 0.0, "camera": None} # The current file's slicer metadata, fetched once per file. meta = {"file": None, "thumbnail": None, "estimate": None} @@ -130,12 +130,12 @@ def make(cfg: dict) -> Module: return {"offline": True} try: status = http(base + QUERY)["result"]["status"] - if down["snapshot"] is None: - down["snapshot"] = _snapshot(http(base + "/server/webcams/list")["result"]["webcams"]) or "" + if down["camera"] is None: + down["camera"] = _camera(http(base + "/server/webcams/list")["result"]["webcams"]) or "" except (OSError, ValueError, KeyError, TypeError): down["until"] = time.monotonic() + BACKOFF return {"offline": True} - st = summarize(status, down["snapshot"] or None) + st = summarize(status, down["camera"] or None) file_meta(st["file"]) st["thumbnail"] = meta["thumbnail"] # Until the file-position estimate settles, the slicer's is better. -- cgit v1.2.3