Cameras on devices (openpanel C1): frames in memory, a binary uplink, demand

- Volatile frames are held in memory with a byte-bounded queue instead of
  the /dev/shm ring scanned on every put; workers keep writing to the
  directory, which is trimmed on a timer. A volatile upload no longer spools
  to disk. ARTIFACT_VOLATILE_BYTES=0 turns the ring off.
- A device sends camera frames as binary messages on its socket, held and
  published in one step and credited to its device node; refused frames are
  logged and dropped. Its snapshot carries demand and the uplink (binary, or
  http through the portal).
- One demand registry for every socket's wanted media, the tunnel's
  included, plus what running flows read, mapped onto on-demand ports and
  published as device_demand; at most 8 media names per socket.
- Per-message deflate off in serve and dev; a device may fetch the last 8
  digests of a message; published dashboards cached per store revision; a
  revoked device socket is closed with 1008.
- Media tiles want bytes only while on screen; the Devices tab previews a
  wired camera; the device node warns when a flow reads an on-demand port.
This commit is contained in:
Melvin Strobl
2026-10-16 20:43:14 +02:00
parent d79203a5f7
commit ecc4bee4bb
29 changed files with 1425 additions and 220 deletions
+26
View File
@@ -173,6 +173,8 @@ curl "$FLUKSIO/artifacts/<digest>" \
The upload returns a reference with the content digest. Identical bytes are
stored once. Node code uses `save_artifact` and `load_artifact` instead.
`?volatile=true` holds the bytes in the engine's memory for a live frame, where
newer frames push them out, instead of storing them.
A superuser can remove stored bytes with `DELETE /artifacts/<digest>`. While
a run, a current message or a flow input's initial value still references the
@@ -249,6 +251,30 @@ Browsers first call `POST /flows/ws-ticket`, then connect with
`?ticket=<ticket>`. Tickets are single-use and expire after 30 seconds.
The socket sends a snapshot followed by node, value, health, and run events.
A client asks for the bytes of the media it shows with
`{"type": "media", "names": [...]}`, which replaces what it asked for before,
for at most 8 messages. Each frame held in memory then arrives as a binary
message before the value naming it: four bytes of header length (big endian),
a JSON header `{"type": "media", "name", "digest", "media_type", "ts"}`, then
the bytes. Messages are sent without per-message compression.
A device's socket is bounded to its own messages. Its snapshot carries
`"demand"`, the ports it reported as `on_demand` that someone wants now, and
`"uplink"`, `"binary"` on its own socket and `"http"` through the portal. When
that set changes it is sent `{"type": "demand", "ports": [...]}`. A port has
demand while a socket asks for the media of the message it is wired to, or a
node of a running flow reads that message.
With a `binary` uplink the device sends a camera frame as a binary message in
the same layout, with the header `{"type": "frame", "name": "<message>",
"media_type": "image/jpeg", "ts": <device ms>}`. The message has to be one of
its `in` ports wired as `image`. The frame is held in memory and published
in one step, credited to its device node. A frame that does not qualify is
logged and dropped and the socket stays open. With `http` it uploads with
`PUT /artifacts?volatile=true&name=<message>` and publishes with
`POST /messages/<message>`. A device that is unpaired or deleted while
connected is closed with 1008.
## Errors and client generation
| Code | Meaning |