forked from k-space/godoor
- cancelKeepOpenDoor() closes the door when /allowed returns null - background goroutine polls /allowed every 15s (KDOORPI_ALLOWED_POLL_INTERVAL) - keepDoorOpenLock mutex guards shared hold state - skip timer rebuild when the hold is unchanged (anti-thrash)
98 lines
2.8 KiB
Go
98 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var keepDoorOpenLock sync.Mutex
|
|
|
|
// keepDoorOpenGen identifies the currently armed hold timer. It is bumped (under
|
|
// keepDoorOpenLock) every time a new timer is installed so that a superseded
|
|
// timer's callback can recognise it is stale and do nothing.
|
|
var keepDoorOpenGen uint64
|
|
|
|
func updateKeepOpenDoor(newKeepOpenTime time.Time) {
|
|
keepDoorOpenLock.Lock()
|
|
defer keepDoorOpenLock.Unlock()
|
|
|
|
// Hold unchanged since the last poll: keep the existing timer rather than
|
|
// rebuilding it every poll, which would float the close time forward by up
|
|
// to one poll interval and re-pulse OpenDoor needlessly.
|
|
if keepDoorOpen.timer != nil && newKeepOpenTime.Equal(keepDoorOpen.until) {
|
|
return
|
|
}
|
|
|
|
// is there one active?
|
|
if keepDoorOpen.timer != nil {
|
|
keepDoorOpen.timer.Stop()
|
|
keepDoorOpen = KeepDoorOpen{}
|
|
}
|
|
|
|
if newKeepOpenTime.After(time.Now()) {
|
|
log.Printf("Keeping door open until %v", newKeepOpenTime)
|
|
if err := OpenDoor(wiegand); err != nil {
|
|
// Don't commit the hold if the relay didn't actually open: leaving
|
|
// keepDoorOpen empty (timer nil) means the next poll retries instead
|
|
// of latching the Equal early-return on a door that never opened.
|
|
log.Printf("ERROR opening door for hold: %v", err)
|
|
return
|
|
}
|
|
keepDoorOpenGen++
|
|
gen := keepDoorOpenGen
|
|
timer := time.AfterFunc(time.Until(newKeepOpenTime), func() {
|
|
handleKeepDoorOpenCloseCleanup(gen)
|
|
})
|
|
keepDoorOpen = KeepDoorOpen{
|
|
timer: timer,
|
|
until: newKeepOpenTime,
|
|
}
|
|
} else {
|
|
if err := CloseDoor(wiegand); err != nil {
|
|
log.Printf("ERROR closing door: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func cancelKeepOpenDoor() {
|
|
keepDoorOpenLock.Lock()
|
|
defer keepDoorOpenLock.Unlock()
|
|
|
|
if keepDoorOpen.timer == nil {
|
|
return
|
|
}
|
|
keepDoorOpen.timer.Stop()
|
|
if err := CloseDoor(wiegand); err != nil {
|
|
// Keep keepDoorOpen non-nil so the next poll's cancel retries the close;
|
|
// clearing it now would strand the door OPEN with no retry path.
|
|
log.Printf("ERROR closing door on hold cancel: %v", err)
|
|
return
|
|
}
|
|
keepDoorOpen = KeepDoorOpen{}
|
|
}
|
|
|
|
func handleKeepDoorOpenCloseCleanup(gen uint64) {
|
|
keepDoorOpenLock.Lock()
|
|
defer keepDoorOpenLock.Unlock()
|
|
|
|
// Timer.Stop() can return after this callback has already started and is
|
|
// blocked here on the lock; by the time we acquire it, updateKeepOpenDoor
|
|
// may have installed a newer hold. Only act if we are still the current
|
|
// generation, otherwise we would close the door and clear a live hold.
|
|
if gen != keepDoorOpenGen {
|
|
return
|
|
}
|
|
|
|
fmt.Println("Keep door open time is reached!")
|
|
if err := CloseDoor(wiegand); err != nil {
|
|
// Leave keepDoorOpen intact so the next poll (which will see
|
|
// keep_open_until=null and call cancelKeepOpenDoor) retries the close
|
|
// instead of stranding the door OPEN.
|
|
log.Printf("ERROR closing door at hold expiry: %v", err)
|
|
return
|
|
}
|
|
keepDoorOpen = KeepDoorOpen{}
|
|
}
|