package main import ( "fmt" "log/slog" "strings" "time" ) import ( "frigate/uptime/notify" "frigate/uptime/ping" ) 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(fmt.Sprintf("%s camera is back online!", camera)) } func (c CameraMonitor) onCameraDown(camera string) { c.SendNotification(camera, false) slog.Info(fmt.Sprintf("%s camera is offline!", camera)) } 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, "online", cameraPingResult.online) c.onCameraPingResult(cameraPingResult.camera, cameraPingResult.online) delete(unknownPingResultCameras, cameraPingResult.camera) // Maintain set of cameras with unknown ping status case <-timeoutChannel: var b strings.Builder for camera := range unknownPingResultCameras { if b.Len() > 0 { b.WriteString(", ") } b.WriteString(camera) } slog.Warn("Timed out waiting for ping result", "cameras", b.String()) 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", "seconds", sleepDuration) time.Sleep(sleepDuration) } }