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:
@@ -511,6 +511,19 @@ function FlowEditorInner({
|
||||
}
|
||||
}, [key])
|
||||
|
||||
// What the flow's nodes read, for a device node to say that a camera read
|
||||
// here sends all the time.
|
||||
const readers = useMemo(() => {
|
||||
const found = new Set<string>()
|
||||
for (const node of definitions) {
|
||||
if (node.type === DEVICE_TYPE) continue
|
||||
for (const spec of node.requires ?? []) {
|
||||
if (spec.name) found.add(qualify(flowName, spec.name))
|
||||
}
|
||||
}
|
||||
return found
|
||||
}, [definitions, flowName])
|
||||
|
||||
/**
|
||||
* Where clicking an endpoint takes you: the thing it stands for.
|
||||
*
|
||||
@@ -1508,6 +1521,7 @@ function FlowEditorInner({
|
||||
nodeTypes={nodeTypeInfo ?? []}
|
||||
origin={flowDoc.origin}
|
||||
suggestions={suggestions}
|
||||
readers={readers}
|
||||
expanded={editorExpanded}
|
||||
onToggleExpand={() => setEditorExpanded((wide) => !wide)}
|
||||
onChange={updateNode}
|
||||
|
||||
@@ -56,6 +56,7 @@ import useAuth from "@/hooks/useAuth"
|
||||
import useCustomToast from "@/hooks/useCustomToast"
|
||||
import { inCodeEditor, useShortcuts } from "@/lib/shortcuts"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { qualify } from "./deriveEdges"
|
||||
import { DtypeValue } from "./FlowBoundary"
|
||||
import {
|
||||
IDENTIFIER,
|
||||
@@ -508,11 +509,14 @@ function DeviceSettings({
|
||||
node,
|
||||
flow,
|
||||
suggestions,
|
||||
readers,
|
||||
onChange,
|
||||
}: {
|
||||
node: NodeDef_Input
|
||||
flow: string
|
||||
suggestions: PortSuggestions
|
||||
/** Qualified messages another node of this flow reads. */
|
||||
readers: ReadonlySet<string>
|
||||
onChange: (next: NodeDef_Input) => void
|
||||
}) {
|
||||
const { data } = useQuery(devicesQueryOptions())
|
||||
@@ -591,6 +595,7 @@ function DeviceSettings({
|
||||
flow={flow}
|
||||
config={config}
|
||||
reported={reported}
|
||||
readers={readers}
|
||||
suggestions={suggestions.provides}
|
||||
onChange={ports("provides")}
|
||||
onConfig={setConfig}
|
||||
@@ -619,6 +624,7 @@ function DevicePortList({
|
||||
flow,
|
||||
config,
|
||||
reported,
|
||||
readers,
|
||||
suggestions,
|
||||
onChange,
|
||||
onConfig,
|
||||
@@ -632,6 +638,8 @@ function DevicePortList({
|
||||
config: Record<string, unknown>
|
||||
/** What the device says it has, or null when it has not said. */
|
||||
reported: ReportedPort[] | null
|
||||
/** Qualified messages another node of this flow reads. */
|
||||
readers?: ReadonlySet<string>
|
||||
suggestions: string[]
|
||||
/** `renamed` is a port taking another name, or dropped (`to` of null). */
|
||||
onChange: (next: MessageSpec[], renamed?: [string, string | null]) => void
|
||||
@@ -809,6 +817,18 @@ function DevicePortList({
|
||||
The device does not report this port.
|
||||
</p>
|
||||
) : null}
|
||||
{said?.on_demand &&
|
||||
spec.name &&
|
||||
readers?.has(qualify(flow, spec.name)) ? (
|
||||
<p
|
||||
className="pl-5.5 text-xs text-status-warning"
|
||||
data-testid="device-port-always-on"
|
||||
>
|
||||
A node in this flow reads every frame, so {port} sends all the
|
||||
time, also while nobody watches. Read snapshot or person
|
||||
instead.
|
||||
</p>
|
||||
) : null}
|
||||
{schema ? (
|
||||
<div className="pl-5.5">
|
||||
<ParamsForm
|
||||
@@ -1771,6 +1791,7 @@ function PanelBody({
|
||||
nodeType,
|
||||
origin,
|
||||
suggestions,
|
||||
readers,
|
||||
expanded,
|
||||
onChange,
|
||||
onRenameMessage,
|
||||
@@ -1783,6 +1804,7 @@ function PanelBody({
|
||||
nodeType: NodeTypeInfo | undefined
|
||||
origin: FlowOrigin | null | undefined
|
||||
suggestions: PortSuggestions
|
||||
readers: ReadonlySet<string>
|
||||
expanded: boolean
|
||||
onChange: (next: NodeDef_Input) => void
|
||||
onRenameMessage: (previous: string, next: string) => void
|
||||
@@ -1954,6 +1976,7 @@ function PanelBody({
|
||||
node={node}
|
||||
flow={flow}
|
||||
suggestions={suggestions}
|
||||
readers={readers}
|
||||
onChange={editNode}
|
||||
/>
|
||||
) : (
|
||||
@@ -2133,6 +2156,7 @@ export function NodePanel({
|
||||
nodeTypes,
|
||||
origin,
|
||||
suggestions,
|
||||
readers,
|
||||
expanded,
|
||||
onChange,
|
||||
onRenameMessage,
|
||||
@@ -2148,6 +2172,8 @@ export function NodePanel({
|
||||
/** Set when the flow was declared in code elsewhere; its bodies are generated. */
|
||||
origin: FlowOrigin | null | undefined
|
||||
suggestions: PortSuggestions
|
||||
/** Qualified messages the flow's nodes read, a device node's aside. */
|
||||
readers: ReadonlySet<string>
|
||||
expanded: boolean
|
||||
onChange: (next: NodeDef_Input) => void
|
||||
onRenameMessage: (previous: string, next: string) => void
|
||||
@@ -2201,6 +2227,7 @@ export function NodePanel({
|
||||
nodeType={nodeType}
|
||||
origin={origin}
|
||||
suggestions={suggestions}
|
||||
readers={readers}
|
||||
expanded={expanded}
|
||||
onChange={onChange}
|
||||
onRenameMessage={onRenameMessage}
|
||||
|
||||
Reference in New Issue
Block a user