113 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			113 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			Go
		
	
	
	
| 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)
 | |
| 	}
 | |
| }
 |