From 9eeab32c8a2eba966d1ad8c1b9e54a86a3321222 Mon Sep 17 00:00:00 2001 From: Darell Tan Date: Sun, 24 Nov 2024 16:03:24 +0800 Subject: [PATCH] Added delayed-off for motion sensors This ensures that the "motion detected" event doesn't flip flop, even if the motion sensor itself was set to a low timeout like 10-30s. It stays in the Detected state until no further detections occur within the timeout period (hardcoded to 1 minute). --- s_motion_sensor.go | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/s_motion_sensor.go b/s_motion_sensor.go index e028537..ea252e8 100644 --- a/s_motion_sensor.go +++ b/s_motion_sensor.go @@ -1,6 +1,9 @@ package hapz2m import ( + "sync" + "time" + "github.com/brutella/hap/accessory" "github.com/brutella/hap/service" ) @@ -8,6 +11,35 @@ import ( // there isn't a distinction between motion and occupancy sensors in z2m, // but most sensors are PIR, so they are technically motion sensors +func setupDelayedOff(m *ExposeMapping, delay time.Duration) { + // lock makes sure Timer and setFunc aren't stepping over each other + lock := &sync.Mutex{} + + tm := time.AfterFunc(1*time.Minute, func() { + lock.Lock() + defer lock.Unlock() + + m.Characteristic.SetValueRequest( /*motion*/ false, nil) + }) + tm.Stop() + + m.SetCharacteristicValueFunc = func(m *ExposeMapping, newVal any, changed bool) (bool, error) { + lock.Lock() + defer lock.Unlock() + + if newVal == true { // motion detected + tm.Stop() + } else { // hold off transitions to no motion + if changed { + tm.Reset(delay) + } + return false, nil + } + + return true, nil + } +} + func createMotionServices(dev *Device) (byte, []*service.S, []*ExposeMapping, error) { var svcs []*service.S var exposes []*ExposeMapping @@ -23,8 +55,11 @@ func createMotionServices(dev *Device) (byte, []*service.S, []*ExposeMapping, er s := service.NewMotionSensor() + m := NewExposeMapping(&exp, s.MotionDetected.C) + setupDelayedOff(m, 1*time.Minute) + svcs = append(svcs, s.S) - exposes = append(exposes, NewExposeMapping(&exp, s.MotionDetected.C)) + exposes = append(exposes, m) } }