README status table was showing transport/discovery/protocol/node as
"not started" when all are done. Added summary sentence, notes column,
config and dev/web rows. Fixed dev/web structure description.
planning.md: removed stale "prerequisite" note about web UI — it is
already implemented and working.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
src/node/main.c: ctrl_enum_cb was discarding menu_count and menu_items,
causing empty dropdowns for all MENU/INTEGER_MENU controls. Added a
menu item pool (MAX_MENU_POOL=128 items) to Ctrl_Build; the callback now
copies items into the pool and sets menu_count/menu_items on the control.
docs/protocol.md: add missing sections — str8 primitive, ENUM_DEVICES,
ENUM_CONTROLS (with control type/flag tables and menu item notes),
GET_CONTROL, and SET_CONTROL schemas.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switching a menu control (e.g. exposure auto → manual) changes the
flags on related controls (e.g. exposure_absolute loses FLAG_GRABBED).
Re-fetch and re-render controls silently after any non-slider change so
the updated enabled/read-only states are reflected immediately.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- index.html: minimal shell with <template> elements for all repeated
DOM structures (node-item, device-group, device-item, ctrl-group,
ctrl-row, capture-badge); links external style.css
- style.css: all styles extracted from index.html
- lib/dom.mjs: by_id, qs, clone, show, hide helpers
- app.mjs: persistent SSE node list replaces Discover button; clicking
a node connects to it; uses clone()/replaceChildren() throughout;
no innerHTML for structure; event wiring at bottom
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- protocol.mjs: all reads/writes switched to LE to match serial.h
- node_client.mjs: persistent error handler prevents ECONNRESET crash
- discovery.mjs: remove unnecessary SO_REUSEPORT
- server.mjs: discovery runs at startup (not per SSE open); uses
EventEmitter + known_peers Map so SSE replays existing peers on connect
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Protocol module: framed binary encoding for control requests/responses
(ENUM_DEVICES, ENUM_CONTROLS, GET/SET_CONTROL, STREAM_OPEN/CLOSE)
- video-node: scans /dev/media* and /dev/video*, serves V4L2 device
topology and controls over TCP; uses UDP discovery for peer announce
- query_cli: auto-discovers a node, queries devices and controls
- protocol_cli: low-level protocol frame decoder for debugging
- dev/web: Express 5 ESM web inspector — live SSE discovery picker,
REST bridge to video-node, controls UI with sliders/selects/checkboxes
- Makefile: sequential module builds before cli/node to fix make -j races
- common.mk: add DEPFLAGS (-MMD -MP) for automatic header dependencies
- All module Makefiles: split compile/link, generate .d dependency files
- discovery: replace 100ms poll loop with pthread_cond_timedwait;
respond to all announcements (not just new peers) for instant re-discovery
- ENUM_DEVICES response: carry device_caps (V4L2_CAP_*) per video node
so clients can distinguish capture nodes from metadata nodes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Config_Entry now holds a union {s, u16, u32, flags} typed at parse time.
Getters read directly from the union — no string conversion at access time.
config_dump reconstructs flag display as 'token | token' from the bitmask.
Separators in flag values: comma, pipe, and whitespace all accepted.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Config_Def schema tables declare section/key/type/default per module.
Typed getters: config_get_str, _u16, _u32, _flags.
FLAGS type parses space/comma-separated tokens via a Config_Flag_Def table.
config_defaults() gives schema defaults without a file.
config_dump() prints effective values for diagnostics.
config_cli: load a file or --defaults and dump effective config.
dev/example.cfg: sample config covering node, discovery, transport.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the receive thread detects a genuinely new peer it sets
early_announce, causing the announce thread to break out of its
sleep and send immediately. The new node receives our announcement
within ~100ms rather than waiting up to interval_ms.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sends 6-byte framed announcements to 224.0.0.251:5353 on startup and
every interval_ms (default 5s). Receive thread maintains a peer table
(max 64 entries); fires on_peer_found for new peers, on_peer_lost when
a peer misses timeout_intervals (default 3) consecutive intervals.
Own announcements are filtered by name+site_id. SO_REUSEADDR+REUSEPORT
allows multiple processes on the same host for testing.
discovery_cli: announce <name> <tcp_port> [flags] — prints found/lost events.
Also notes future config module in planning.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both sites default to site_id=0; gateway must rewrite site_id in
announcements and protocol messages crossing the boundary. site_id=0
on a cross-site link is a gateway protocol error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers all layers: TCP → transport frame → message type dispatcher →
payload schemas. Documents frame format (6-byte header), all message
types, STREAM_OPEN/CLOSE lifecycle, codec/pixel_format/origin tables,
stream events, and discovery announcement wire format.
Also fixes stale channel_id reference in audio section of architecture.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes channel_id from the header. All message-specific identifiers
(stream_id, request_id, etc.) now live at the start of the payload,
interpreted by each message type handler. A relay seeing an unknown
type can skip or forward it using only payload_length, with no
knowledge of the payload structure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
transport_server_create/start: binds TCP, spawns accept thread, closes
excess inbound connections when max_connections is reached.
transport_connect: outbound TCP, spawns read thread before returning.
transport_send_frame: packs 8-byte header with serial put_*, then writes
header + payload under a per-connection mutex (thread-safe).
Read thread: reads header, validates payload_length <= max_payload, mallocs
payload, calls on_frame (callback owns and must free payload). On error or
disconnect calls on_disconnect then frees conn.
transport_cli: server mode echoes received frames; client mode sends 3
test frames and prints echoes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
No central message hub or broker. Controller is a function_flags bit — any
node with a user interface (e.g. web UI) holds it. Multiple nodes can hold
the role simultaneously. Controller communicates directly with peers over
the binary protocol; no intermediary.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ingest module: dequeue V4L2 buffers, emit one encapsulated frame per buffer.
Driver guarantees per-buffer framing for V4L2_PIX_FMT_MJPEG; no scanning needed.
mjpeg_scan: future optional module for non-compliant hardware only.
Explicitly a workaround, not part of the primary pipeline.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
V4L2 with proper node: driver guarantees per-buffer framing, no scan needed.
Opaque pipe (dd|nc): buffer boundaries lost, EOI scanner is the correct tool.
Weird containers (RIFF-wrapped USB cams, IP cameras, RTSP): route via ffmpeg,
not a custom parser. Scanner is an option only for constrained raw-stream cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
format (u16): what the bytes are — drives decode, stable across encoder changes
pixel_format (u16): layout for raw formats, ignored otherwise
origin (u16): how it was produced — informational only, no effect on decode
Eliminates numerical range assumptions (0x01xx ffmpeg range). A camera
outputting MJPEG natively and libjpeg-turbo encoding MJPEG are the same
format with different origins; receiver handles both identically.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
0x01xx range reserved for ffmpeg-backed formats (H.265, AV1, FFV1,
ProRes). Documents libavcodec vs subprocess trade-offs: subprocess suits
archival completeness paths, libavcodec suits low-latency encode. Receiver
only cares about wire format, not which encoder produced it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
xorg module: XRandR geometry queries, screen grab source (XShmGetImage),
frame viewer sink (XShmPutImage, fullscreen per monitor). All exposed as
standard source/sink node roles on the existing transport.
Audio: deferred but transport is already compatible — channel_id mux,
audio_frame message type slot reserved, relay/allocator are payload-agnostic.
Also marks serial as done in planning.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
site_id (u16) is reserved in the announcement payload from day one,
always 0 in single-site deployments. Documents the site gateway node
concept and fully-qualified addressing (site_id:namespace:instance) so
multi-site can be added later without wire format changes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A node can declare multiple roles simultaneously (e.g. relay + sink).
Replaces the function string with a fixed-size flags field; keeps the
payload layout simple and fixed-width up to the name.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reuse UDP multicast transport (224.0.0.251:5353) with our own binary
wire format — no Avahi, no Bonjour, no daemon dependency.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- dev/web/ is the browser-side equivalent of dev/cli/ — a Node.js dev UI
that speaks the binary protocol and exposes V4L2/topology inspection
- Explicitly noted as blocked on transport+protocol being finalised
- Distinguished from the future production dashboard
- Directory structure updated to show serial/transport/protocol modules
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
architecture.md:
- Web interface connects as a first-class binary protocol peer; no JSON
bridge in C; DataView in JS maps directly to get_u32 etc.
- Future preprocessor section: protocol schema defined once, emits both
C (put/get, write_*/read_*) and ESM JS (DataView encode/decode);
same tool as planned for error location codes
planning.md:
- Add web node (entry #11) to module order
- Add Future: Protocol Preprocessor section above Deferred Decisions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
put_u8/16/32/64 and get_u8/16/32/64 (plus signed variants) for packing
and unpacking values at explicit byte offsets in a buffer. Each value is
encoded byte-by-byte with explicit shift operations — no struct casting,
no alignment assumptions, correct on any host endianness.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CC, CFLAGS, and BUILD are now defined once in common.mk at the repo root.
Each module and CLI Makefile sets ROOT then includes common.mk, eliminating
the repeated definitions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
architecture.md: replace JSON control payloads with binary serialization;
add Protocol Serialization section describing little-endian wire format,
put/get buffer layer, and write_*/read_* protocol layer.
planning.md: mark common/media_ctrl/v4l2_ctrl done; insert serial (#4)
and protocol (#6) modules with descriptions.
conventions.md: document -flto and its implication (no manual static for
inlining — compiler handles it at link time).
Makefiles: add -flto to CFLAGS in all four Makefiles.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The CLI is a separate translation unit that doesn't include <linux/media.h>,
so the kernel flag constants were undefined there. Fix by defining our own
public constants in media_ctrl.h:
- MEDIA_LINK_FL_{ENABLED,IMMUTABLE,DYNAMIC} for link flags
- MEDIA_PAD_FLAG_{SINK,SOURCE} for pad flags (distinct names avoid
redefinition conflict with kernel's MEDIA_PAD_FL_* in media_ctrl.c)
media_ctrl.c now translates kernel flags to our constants on the way out.
Added fallback #defines for kernel constants that were missing them.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Modules (src/modules/):
- common/error: App_Error struct with structured detail union, error codes,
app_error_print(); designed for future upgrade to preprocessor-generated
location codes
- media_ctrl: media device enumeration, topology query (entities/pads/links),
link enable/disable via Media Controller API (/dev/media*)
- v4l2_ctrl: control enumeration (with menu item fetching), get/set via
V4L2 ext controls API, device discovery (/dev/video*)
All modules use -std=c11 -D_GNU_SOURCE, build artifacts go to build/ only.
Kernel-version-dependent constants guarded with #ifdef + #warning.
CLI drivers (dev/cli/):
- media_ctrl_cli: list, info, topology, set-link subcommands
- v4l2_ctrl_cli: list, controls, get, set subcommands
Docs (docs/cli/):
- media_ctrl_cli.md and v4l2_ctrl_cli.md with usage, examples, and
context within the video routing system
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move modules under src/modules/ and public headers under include/,
keeping dev/, tests/, and docs at the top level. Adds a placeholder
for src/node/ where the final video node entry point will live.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Initial documentation for the multi-peer video routing system:
- architecture.md covers graph model, transport protocol, relay design,
and the Pi get-it-on-the-wire-first rationale
- planning.md defines module build order and directory structure
- conventions.md captures C11 code style, naming, error handling approach,
and directory layout rules
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>