Merge pull request #9 from sharedjourney/feat/axis-vmd4-motion-topic

feat(event/stream): classify AXIS VMD 3 and VMD 4 topics as motion
This commit is contained in:
Cédric Verstraeten
2026-08-05 12:51:08 +02:00
committed by GitHub
2 changed files with 34 additions and 3 deletions

View File

@@ -79,10 +79,19 @@ var topicRules = []struct {
// https://developer.axis.com/vapix/network-video/event-and-action-services/
{"MotionRegionDetector/Motion", KindMotion},
// AXIS Guard suite — vendor analytics apps with Camera<N>Profile<ID>
// suffixes. Treated as motion so they can drive motion-triggered
// recording on cameras using these apps instead of basic VMD.
// tns1:RuleEngine/tnsaxis:VMD3/vmd3_video_<N> — AXIS VMD 3, the
// firmware-builtin predecessor of the VMD 4 ACAP. It lives under
// RuleEngine, not CameraApplicationPlatform, so the VMD rule below
// does not cover it. Still shipping on deployed cameras.
// https://developer.axis.com/vapix/network-video/event-and-action-services/
{"RuleEngine/VMD3/", KindMotion},
// AXIS ACAP motion apps with Camera<N>Profile<ID> suffixes. VMD 4 is
// the stock app shipped on the camera; the Guard suite are the
// paid analytics products. Prefix-match because of the suffix.
// https://developer.axis.com/vapix/applications/vmd4
// https://developer.axis.com/vapix/applications/motion-guard
{"CameraApplicationPlatform/VMD/", KindMotion},
{"CameraApplicationPlatform/MotionGuard/", KindMotion},
{"CameraApplicationPlatform/FenceGuard/", KindMotion},
{"CameraApplicationPlatform/LoiteringGuard/", KindMotion},

View File

@@ -25,6 +25,18 @@ func TestClassifyTopic(t *testing.T) {
{"axis_fence_guard", "tnsaxis:CameraApplicationPlatform/FenceGuard/Camera1ProfileANY", KindMotion},
{"axis_loitering_guard", "tnsaxis:CameraApplicationPlatform/LoiteringGuard/Camera1ProfileANY", KindMotion},
// AXIS VMD 4 — the stock motion app, and the one an installer
// reaches for before any Guard product. The profile suffix
// varies with the configured VMD profile.
{"axis_vmd4_profile_any", "tnsaxis:CameraApplicationPlatform/VMD/Camera1ProfileANY", KindMotion},
{"axis_vmd4_profile_numbered", "tnsaxis:CameraApplicationPlatform/VMD/Camera1Profile1", KindMotion},
// AXIS VMD 3 — the firmware-builtin predecessor, published under
// RuleEngine rather than CameraApplicationPlatform. Still
// shipping on deployed cameras.
{"axis_vmd3_video_1", "tns1:RuleEngine/tnsaxis:VMD3/vmd3_video_1", KindMotion},
{"axis_vmd3_video_2", "tns1:RuleEngine/tnsaxis:VMD3/vmd3_video_2", KindMotion},
// --- Tampering --------------------------------------------------
{"tamper_detector", "tns1:RuleEngine/TamperDetector/Tamper", KindTampering},
@@ -88,6 +100,16 @@ func TestClassifyTopic(t *testing.T) {
{"relay_failure_not_digital_output", "tns1:Device/HardwareFailure/RelayFailure", KindUnknown},
{"digital_input_config_not_digital_input", "tns1:Device/IO/DigitalInputConfiguration", KindUnknown},
{"tamper_detector_log_not_tampering", "tns1:Device/Diagnostics/TamperDetectorLog", KindUnknown},
// The two AXIS VMD needles carry a trailing slash so they match a
// whole path segment. Without it, any sibling app or rule whose
// name merely starts with VMD / VMD3 would classify as motion and
// drive recording.
{"vmd_statistics_app_not_motion", "tnsaxis:CameraApplicationPlatform/VMDStatistics/Camera1", KindUnknown},
{"vmd3_config_rule_not_motion", "tns1:RuleEngine/tnsaxis:VMD3Config/Changed", KindUnknown},
// VMD3 is scoped to RuleEngine; the same name under another
// container is a different thing.
{"vmd3_outside_rule_engine_not_motion", "tnsaxis:Storage/VMD3/Status", KindUnknown},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {