Restore motion threshold defaults

Treat nil, zero, and negative pixel-change thresholds as the historical default of 150, removing the disabled-motion behavior and adding configuration tests.
This commit is contained in:
Cédric Verstraeten
2026-08-05 14:04:46 +02:00
parent 2092f3e49d
commit 704011c20b
3 changed files with 53 additions and 23 deletions

View File

@@ -24,17 +24,12 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf
var motionRectangle models.MotionRectangle
var motionRectangles []models.MotionRectangle
// Resolve the motion sensitivity (pixel-change threshold):
// nil (unset) -> default 150
// 0 -> motion detection DISABLED (temporary off switch from the UI)
// > 0 -> trigger when the number of changed pixels exceeds it
// Resolve the motion sensitivity (pixel-change threshold). Nil, zero, and
// negative values use the historical default so older configurations keep
// recording after an upgrade.
pixelThreshold := 150
motionDisabled := false
if config.Capture.PixelChangeThreshold != nil {
if config.Capture.PixelChangeThreshold != nil && *config.Capture.PixelChangeThreshold > 0 {
pixelThreshold = *config.Capture.PixelChangeThreshold
if pixelThreshold <= 0 {
motionDisabled = true
}
}
// In motion mode we always run detection. In CONTINUOUS mode recording is
// 24/7 so motion detection is normally skipped, BUT if a motion region is
@@ -45,11 +40,7 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf
continuousMode := config.Capture.Continuous == "true"
hasMotionRegion := config.Region != nil && len(config.Region.Polygon) > 0
if motionDisabled {
log.Log.Warning("computervision.main.ProcessMotion(): motion detection is DISABLED because pixelChangeThreshold is set to 0 or less (nil/unset would default to 150). If motion detection is expected to be running, set capture.pixelChangeThreshold to a positive value (150 recommended) or AGENT_CAPTURE_PIXEL_CHANGE, then restart/update the agent.")
} else if continuousMode && !hasMotionRegion {
if continuousMode && !hasMotionRegion {
log.Log.Info("computervision.main.ProcessMotion(): continuous recording enabled and no motion region configured, so no motion detection required.")
@@ -236,9 +227,9 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf
// a reference square of sqrt(threshold) px (in this MOTION
// frame's pixel space) so the user can visually gauge how
// large a moving object must be before it is detected.
"pixelChangeThreshold": pixelThreshold,
"pixelChangeThreshold": pixelThreshold,
},
},
},
}
payload, err := models.PackageMQTTMessage(configuration, message)
if err == nil {

View File

@@ -651,13 +651,12 @@ func applyAgentEnvVars(configuration *models.Configuration, prefix string, apply
}
}
// Motion sensitivity: nil/unset must still resolve to the default (150), not
// be left nil. An explicit 0 (temporary "disable motion detection" switch
// from the UI) is a real, non-nil value and must NOT be touched here. Only
// applied for the effective configuration (applyDefaults), not for the
// separate global/custom views, so a missing value in one layer can still be
// inherited from the other instead of being masked by this default.
if applyDefaults && configuration.Config.Capture.PixelChangeThreshold == nil {
// Motion sensitivity historically used 0 to mean "use the default". Preserve
// that behaviour for configurations created before this field became a
// pointer, and also recover invalid negative values. Only apply this to the
// effective configuration so missing values can still be inherited between
// the separate global and custom layers.
if applyDefaults && (configuration.Config.Capture.PixelChangeThreshold == nil || *configuration.Config.Capture.PixelChangeThreshold <= 0) {
defaultPixelChangeThreshold := 150
configuration.Config.Capture.PixelChangeThreshold = &defaultPixelChangeThreshold
}

View File

@@ -0,0 +1,40 @@
package config
import (
"testing"
"github.com/kerberos-io/agent/machinery/src/models"
)
func TestApplyAgentEnvVarsPixelChangeThresholdDefault(t *testing.T) {
tests := []struct {
name string
threshold *int
want int
}{
{name: "missing", want: 150},
{name: "legacy zero", threshold: intPointer(0), want: 150},
{name: "negative", threshold: intPointer(-1), want: 150},
{name: "positive", threshold: intPointer(275), want: 275},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
configuration := &models.Configuration{}
configuration.Config.Capture.PixelChangeThreshold = test.threshold
applyAgentEnvVars(configuration, "TEST_", true)
if configuration.Config.Capture.PixelChangeThreshold == nil {
t.Fatal("PixelChangeThreshold is nil after applying defaults")
}
if got := *configuration.Config.Capture.PixelChangeThreshold; got != test.want {
t.Fatalf("PixelChangeThreshold = %d, want %d", got, test.want)
}
})
}
}
func intPointer(value int) *int {
return &value
}