Skip to content

feat: cross-stream motion trigger for dual-lens cameras (fixes #334)#336

Merged
matteius merged 3 commits intomainfrom
feature/cross-stream-motion-trigger
Mar 26, 2026
Merged

feat: cross-stream motion trigger for dual-lens cameras (fixes #334)#336
matteius merged 3 commits intomainfrom
feature/cross-stream-motion-trigger

Conversation

@matteius
Copy link
Contributor

Summary

Implements cross-stream motion triggering to address the dual-lens camera use-case raised in #334 (TP-Link/Tapo C545D and similar cameras where one lens provides ONVIF events and the other PTZ lens does not).

A new motion_trigger_source field on each stream allows it to be slaved to another stream's ONVIF motion events. When the source stream detects motion (start or end), the same event is automatically propagated to every stream that lists it as its trigger source.

How it works

  1. Set motion_trigger_source on the PTZ stream to the exact name of the Fixed Wide stream.
  2. lightNVR's ONVIF poller detects motion on the Fixed Wide stream and calls process_motion_event("C545D-Fixed", true/false, ...).
  3. process_motion_event now scans all configured streams and re-queues the event for any stream whose motion_trigger_source == "C545D-Fixed" -- in this case the PTZ stream.
  4. The PTZ stream's normal motion-recording state machine takes over: it starts recording on motion-start and stops (with its configured post-buffer) on motion-end.

Files changed

File Change
include/core/config.h Add motion_trigger_source[MAX_STREAM_NAME] to stream_config_t
db/migrations/0039_add_motion_trigger_source.sql New migration: ALTER TABLE streams ADD COLUMN motion_trigger_source
include/database/db_embedded_migrations.h Embed migration 0039, bump EMBEDDED_MIGRATIONS_COUNT to 39
src/database/db_streams.c Include new field in all INSERT, UPDATE, and SELECT paths with correct column enum entries
src/video/onvif_motion_recording.c Propagate motion events to linked streams inside process_motion_event()
src/web/api_handlers_streams_get.c Expose motion_trigger_source in all three GET endpoints
src/web/api_handlers_streams_modify.c Parse motion_trigger_source in POST (create) and PUT (update) handlers

Usage (C545D example)

After adding both streams, slave the PTZ stream to the Fixed Wide stream via the API:

PUT /api/streams/C545D-PTZ
{ "motion_trigger_source": "C545D-Fixed" }

Both streams must have ONVIF motion recording enabled. The PTZ stream's pre_buffer_seconds and post_buffer_seconds settings apply as normal to its triggered recordings.

Known limitation

As noted in the original issue: if the moving object leaves the Fixed Wide's field of view while the PTZ is still tracking it, the Fixed Wide sends a motion-end event which begins the PTZ stream's post-buffer countdown. The post-buffer provides a configurable grace period before the recording actually stops.

Testing

  • Builds cleanly with no new warnings
  • Manual test with a dual-lens ONVIF camera recommended
  • Verify DB migration applies cleanly on an existing database

Pull Request opened by Augment Code with guidance from the PR author

Add a motion_trigger_source field to stream_config_t that allows a stream's
ONVIF motion-based recording to be triggered by motion events detected on a
different stream.

This is specifically designed for dual-lens cameras such as the TP-Link/Tapo
C545D, where the fixed wide-angle lens exposes ONVIF motion events but the
PTZ tracking lens does not. By setting motion_trigger_source on the PTZ stream
to the name of the fixed-lens stream, both recordings start and stop together.

Changes:
- include/core/config.h: add char motion_trigger_source[MAX_STREAM_NAME] to
  stream_config_t
- db/migrations/0039_add_motion_trigger_source.sql: new migration adding
  motion_trigger_source TEXT DEFAULT '' column to streams table
- include/database/db_embedded_migrations.h: embed migration 0039, bump
  EMBEDDED_MIGRATIONS_COUNT to 39
- src/database/db_streams.c: include motion_trigger_source in all INSERT,
  UPDATE and SELECT queries with correct column index enums
- src/video/onvif_motion_recording.c: after queuing a motion event for the
  primary stream, propagate the same event (start or end) to every stream
  whose motion_trigger_source matches the current stream name
- src/web/api_handlers_streams_get.c: expose motion_trigger_source in all
  three GET endpoints (list, single, full)
- src/web/api_handlers_streams_modify.c: parse motion_trigger_source in both
  POST (create) and PUT (update) stream handlers

Closes #334
@codecov
Copy link

codecov bot commented Mar 25, 2026

Codecov Report

❌ Patch coverage is 54.73684% with 43 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/video/onvif_motion_recording.c 0.00% 19 Missing ⚠️
src/web/api_handlers_streams_modify.c 0.00% 16 Missing ⚠️
src/database/db_streams.c 68.75% 5 Missing ⚠️
src/web/api_handlers_streams_get.c 0.00% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

Addresses Codecov patch coverage gap (was 20.37%, 43 uncovered lines)
across four changed files.

test_db_streams.c — 4 new tests (covers db_streams.c missing lines):
  - test_motion_trigger_source_defaults_empty
  - test_motion_trigger_source_round_trip
  - test_motion_trigger_source_update
  - test_motion_trigger_source_in_get_all

test_cross_stream_motion_trigger.c — new Layer 3 test binary (covers
onvif_motion_recording.c cross-stream propagation block):
  - test_process_motion_event_null_stream_returns_error
  - test_process_motion_event_no_linked_streams
  - test_process_motion_event_propagates_to_linked_stream
  - test_process_motion_event_end_propagates_to_linked_stream
  - test_process_motion_event_unregistered_source_no_crash

test_api_handlers_system.c — 2 new tests (covers api_handlers_streams_get.c
and api_handlers_streams_modify.c new lines):
  - test_handle_get_streams_includes_motion_trigger_source
  - test_handle_put_stream_parses_motion_trigger_source

CMakeLists.txt — register test_cross_stream_motion_trigger as Layer 3 target
@matteius matteius merged commit 119de9f into main Mar 26, 2026
4 checks passed
@origin2000
Copy link

I have to admit I don't know where and how to put this configuration:

PUT /api/streams/C545D-PTZ
{ "motion_trigger_source": "C545D-Fixed" }

Edit Stream->Recording Settings-Recording Mode:

  • Enable AI Detection-Based Recording CHECKED
  • AI Detection Settings-> Using Default API Endpoint: IS A FULL URL, HOW DOES THIS FIT?

lightnvr.ini under [api_detection] also requires a URL

Please advise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants