package main import ( "log/slog" "time" ) import ( "frigate/uptime/ping" "frigate/uptime/notify" ) type CameraMonitor struct { pingInterval time.Duration pingTimeout time.Duration consecutiveDownThreshold int downtime map[string]int notify.Notifier } func NewCameraMonitor(pingInterval time.Duration, pingTimeout time.Duration, consecutiveDownThreshold int, notifier notify.Notifier) CameraMonitor { return CameraMonitor{ pingInterval: pingInterval, pingTimeout: pingTimeout, consecutiveDownThreshold: consecutiveDownThreshold, downtime: make(map[string]int), Notifier: notifier, } } func (c CameraMonitor) onCameraUp(camera string) { c.SendNotification(camera, true) slog.Info(camera, "camera is back online!") } func (c CameraMonitor) onCameraDown(camera string) { c.SendNotification(camera, false) slog.Info(camera, "camera is offline!") } func (c CameraMonitor) onCameraPingResult(camera string, online bool) { if online { if c.downtime[camera] >= c.consecutiveDownThreshold { c.onCameraUp(camera) } c.downtime[camera] = 0 } else { c.downtime[camera] += 1 if c.downtime[camera] == c.consecutiveDownThreshold { c.onCameraDown(camera) } } } func (c CameraMonitor) Run(cameras map[string]string) { type pingResult struct { camera string online bool } var pingResultChannel = make(chan pingResult, 4) for { var unknownPingResultCameras = make(map[string]bool, len(cameras)) var startTime = time.Now() var timeoutChannel = time.After(c.pingTimeout) // Start all pings for camera, host := range cameras { go func() { pingResultChannel <- pingResult{ camera: camera, online: ping.VideoPing(host), } }() unknownPingResultCameras[camera] = true } timeout: // Await ping results or timeout for range cameras { select { case cameraPingResult := <-pingResultChannel: slog.Debug(cameraPingResult.camera, "camera:", cameraPingResult.online) c.onCameraPingResult(cameraPingResult.camera, cameraPingResult.online) delete(unknownPingResultCameras, cameraPingResult.camera) // Maintain set of cameras with unknown ping status case <-timeoutChannel: slog.Info("Timed out waiting for cameras", unknownPingResultCameras) break timeout } } // Handle timed out camera pings for camera := range unknownPingResultCameras { c.onCameraPingResult(camera, false) } var sleepDuration = c.pingInterval - time.Since(startTime) slog.Debug("Sleeping for", sleepDuration) time.Sleep(sleepDuration) } }