Skip to content

feat(zmnext): drive and ingest the per-monitor zm-next worker#9

Merged
SteveGilvarry merged 4 commits into
masterfrom
feat/zmnext-worker-integration
Jun 22, 2026
Merged

feat(zmnext): drive and ingest the per-monitor zm-next worker#9
SteveGilvarry merged 4 commits into
masterfrom
feat/zmnext-worker-integration

Conversation

@SteveGilvarry

Copy link
Copy Markdown
Owner

Wire zm-api to orchestrate zm-next (zm-core) over the existing per-monitor stream-socket protocol. Off by default ([zmnext].enabled), reversible per camera; zero behaviour change until enabled.

What's here

Task Area Where
1 EVENT (0x06) + Monitor stream parsing streaming/source/protocol.rs, stream_socket.rs
1 Router → ingest sink streaming/source/router.rs
5 EVENT → Events/Frames ingest service/zmnext/{detail,ingest}.rs
2 Daemon spawns/supervises the worker daemon/manager.rs
3 Pipeline JSON generator service/zmnext/pipeline.rs
4 Per-monitor UseZmNext flag (graceful) repo/monitors.rs::use_zmnext
Event-id assignment handshake protocol 0x11/0x0304, reader write-half, router ControlReply, ingest
Config + wiring + runbook configure/zmnext.rs, server/state.rs, docs/ZMNEXT_TASKS.md

Design highlights

  • Media path untouched. EVENT (0x06) on the new Monitor stream routes to ingest; WebRTC/HLS/MSE consume only Video/Audio as before. Unknown types/tags skip per the additive protocol.
  • Event-id assignment handshake. zm-api owns the ZoneMinder event id end to end and hands it (plus the exact Medium-scheme path) to store_event at clip-open via a 0x11 Command, so clips land natively in ZM's tree — no schema change, no file relocation. recording_saved finalizes by the echoed event_id.
  • UseZmNext flag is graceful. Read via an isolated query that degrades to false (legacy) on a missing column, so this code is inert until the ZoneMinder fork ships the column — and activates automatically once it exists. The monitors SeaORM entity is deliberately not widened.
  • Daemon integration reuses the existing ManagedProcess supervision (SIGTERM stop, backoff, watchdog, reconcile); "reload" = regenerate pipeline JSON + restart. Flipping the flag swaps daemons cleanly.

Tests / gates

cargo fmt clean · cargo clippy --all-targets --all-features -D warnings clean · 572 lib tests pass (incl. EVENT parsing, the control round-trip over a real socket, pipeline generation, ingest helpers).

Cross-repo coordination (not in this PR)

  1. Monitors.UseZmNext TINYINT NOT NULL DEFAULT 0 — ZoneMinder fork migration. Until it lands, everything stays legacy.
  2. store_event handshake — zm-next must emit recording_opening, consume assign_recording (stage-then-rename), and echo event_id in recording_saved. Full contract in docs/ZMNEXT_TASKS.md.

🤖 Generated with Claude Code

SteveGilvarry and others added 2 commits June 22, 2026 20:36
Wire zm-api up to orchestrate zm-next (zm-core) over the existing
per-monitor stream-socket protocol. Off by default ([zmnext].enabled),
reversible per camera; zero behaviour change until enabled.

- protocol: add EVENT (0x06), StreamId::Monitor, MonitorEvent + parse_event
  (skip-on-unknown). Reader routes EVENT to SocketEvent::MonitorEvent and
  skips media/HELLO on the monitor stream; media path unchanged.
- router: MonitorEventEnvelope + set_event_sink; reader forwards EVENTs to a
  bounded ingest channel (try_send, never stalls media).
- ingest: per-monitor session state machine maps detection/description/
  recording_saved onto Events/Frames rows (service/zmnext).
- daemon: spawn/supervise the zm-core worker per monitor (stable id
  `zm-core --monitor-id N`, SIGTERM stop, reload = regen pipeline + restart),
  branched into start/stop/reconcile; zm-core whitelisted in spec validation.
- pipeline: generate capture_rtsp_multi -> decode_detect -> store_event JSON
  from Monitors + Zones.
- flag: repo::monitors::use_zmnext reads UseZmNext in isolation, degrading to
  legacy on a missing column (fork migration pending); entity not widened.
- config: [zmnext] section; AppState wires the ingest task + daemon runtime.
- docs/ZMNEXT_TASKS.md: runbook + open coordination items.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make zm-api own the event id end to end and hand it to zm-next's
store_event at clip-open, so clips land natively in ZoneMinder's tree
with no schema change and no file relocation.

- protocol: EVENT_RECORDING_OPENING (0x0304), MSG_TYPE_COMMAND (0x11),
  and build_control_message (client->server encoder on the Monitor stream).
- reader: split the connection into read/write halves; take_writer() hands
  the write half to the router task so replies don't contend with the read loop.
- router: ControlReply (best-effort send_command_json) carried on every
  MonitorEventEnvelope; reader inner loop is now a biased select! that drains
  queued replies and writes them on the same connection. Real-socket
  round-trip test added.
- ingest: recording_opening allocates/adopts the Events row, computes the
  Medium-scheme dir + video_name from the resolved storage path, sets
  Scheme/DefaultVideo, and replies assign_recording; recording_saved finalizes
  by the echoed event_id. detail gains RecordingOpeningDetail + event_id.
- docs: ZMNEXT_TASKS.md specifies the 3-step store_event contract the fork
  must implement (recording_opening -> assign_recording (stage-then-rename)
  -> echo event_id in recording_saved).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Thank you for your pull request. Before it can be merged, please agree to the Contributor License Agreement.


I have read the CLA Document and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

SteveGilvarry and others added 2 commits June 22, 2026 22:05
…EventClip fields

Apply the zm-next deltas: the two recorders merged into one mode-switched
`store` plugin, and the EVENT contract gained trigger/cause + richer EventClip.

- pipeline: emit a single `store` plugin with a `mode` derived from the monitor
  function (Record->continuous, Mocord->both, Modect/Nodect->event; default
  continuous). continuous/both emit max_secs; event/both emit pre/post-roll,
  max_buffer_sec, trigger_types. New StoreMode enum + config tunables. Documents
  the same-filesystem requirement for the assigned `dir` vs store `root`.
- ingest: set event Cause from the recording_opening `trigger`
  (continuous->Continuous, motion->Motion, detection/tracked_detection->
  Detection, audio_event->Audio, else passthrough). recording_saved now treats
  event_id 0/absent as unassigned (assigned_event_id()) and falls back to the
  open session or a fresh row, carrying the EventClip `cause`. duration stays
  seconds.
- detail: RecordingSavedDetail gains `cause` + assigned_event_id(); doc updates
  for the merged `store` plugin and the recording_opening trigger semantics.
- docs: ZMNEXT_TASKS.md gains the store-mode mapping table, the same-filesystem
  note, and the updated recording_opening / EventClip JSON shapes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per zm-next: EVENTs (triggers, assign_recording) flow on a process-global
bus independent of tree position, while tree position decides which FRAMES a
plugin sees. Under decode_detect, store would record the low-res substream;
as a child of capture it records the captured main stream and still receives
event-mode triggers over the bus. output_mqtt likewise becomes a sibling.

- pipeline: decode_detect, store, output_mqtt are now siblings under the
  capture root (was store/mqtt nested under decode_detect). Module doc + tests
  updated to the sibling layout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@SteveGilvarry SteveGilvarry merged commit f76e293 into master Jun 22, 2026
3 of 5 checks passed
@SteveGilvarry SteveGilvarry deleted the feat/zmnext-worker-integration branch June 22, 2026 12:40
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant