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:
@@ -19,6 +19,7 @@ import {
|
||||
FlowsService,
|
||||
} from "@/client"
|
||||
import { initials, resolveIcon } from "@/components/Dashboard/icons"
|
||||
import { MediaView } from "@/components/Dashboard/MediaWidget"
|
||||
import { dashboardsQueryOptions } from "@/components/Dashboard/queries"
|
||||
import { flowsQueryOptions } from "@/components/Flow/queries"
|
||||
import { InfoTip, PanelSection } from "@/components/Flow/SidePanel"
|
||||
@@ -139,6 +140,7 @@ export function DeviceDetail({ id }: { id: string }) {
|
||||
<section className={cn(CARD, "grid gap-4")}>
|
||||
<Basics device={device} canEdit={canEdit} onChange={write} />
|
||||
</section>
|
||||
<Camera device={device} />
|
||||
<section className={CARD}>
|
||||
<Dashboards device={device} canEdit={canEdit} onChange={write} />
|
||||
</section>
|
||||
@@ -216,6 +218,38 @@ function Basics({
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* What the device's camera sees, once a flow wires its `camera` port.
|
||||
*
|
||||
* Watching is what turns the camera on: it sends frames only while this
|
||||
* preview, a tile or a flow reading them wants them.
|
||||
*/
|
||||
function Camera({ device }: { device: DevicePublic }) {
|
||||
const bound = (device.bindings ?? []).find(
|
||||
(binding) => binding.port === "camera" && binding.direction === "in",
|
||||
)
|
||||
if (!bound) return null
|
||||
return (
|
||||
<section className={CARD}>
|
||||
<PanelSection
|
||||
title="Camera"
|
||||
help="Live while this preview is on screen. The camera sends frames only while someone watches them here or on a dashboard, or a running flow reads them."
|
||||
>
|
||||
<div
|
||||
className="flex aspect-video w-full overflow-hidden rounded-md bg-muted text-center text-sm"
|
||||
data-testid="device-camera"
|
||||
>
|
||||
<MediaView
|
||||
message={bound.message}
|
||||
fit="contain"
|
||||
label={`${device.title || device.id} camera`}
|
||||
/>
|
||||
</div>
|
||||
</PanelSection>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
/** Which dashboards the device shows, in the order its rail carries them. */
|
||||
function Dashboards({
|
||||
device,
|
||||
|
||||
Reference in New Issue
Block a user