Setup repeat camera down notifications #24
This commit is contained in:
		
							parent
							
								
									7b24dad5b9
								
							
						
					
					
						commit
						8e99fb8022
					
				| 
						 | 
					@ -4,12 +4,12 @@ import "os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "gopkg.in/yaml.v3"
 | 
					import "gopkg.in/yaml.v3"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
type Config struct {
 | 
					type Config struct {
 | 
				
			||||||
	Cameras map[string]string `yaml:"cameras"`
 | 
						Cameras                     map[string]string `yaml:"cameras"`
 | 
				
			||||||
	PingIntervalSeconds int64 `yaml:"ping_interval_s"`
 | 
						PingIntervalSeconds         int64             `yaml:"ping_interval_s"`
 | 
				
			||||||
	PingTimeoutSeconds int64 `yaml:"ping_timeout_s"`
 | 
						PingTimeoutSeconds          int64             `yaml:"ping_timeout_s"`
 | 
				
			||||||
	ConsecutiveDownThreshold int `yaml:"consecutive_down_threshold"`
 | 
						ConsecutiveDownThreshold    int               `yaml:"consecutive_down_threshold"`
 | 
				
			||||||
 | 
						NotificationRepeatThreshold int               `yaml:"notification_repeat_threshold"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ReadConfig(configFilename string) Config {
 | 
					func ReadConfig(configFilename string) Config {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,8 @@
 | 
				
			||||||
cameras:
 | 
					cameras:
 | 
				
			||||||
  back_door: 'rtsp://frigate:8554/back_door'
 | 
					  back_door: 'rtsp://frigate:8554/back_door'
 | 
				
			||||||
  doorbell: 'rtsp://frigate:8554/doorbell'
 | 
					  doorbell: 'rtsp://frigate:8554/doorbell'
 | 
				
			||||||
  front_door: 'rtsp://frigate:8554/front_door'
 | 
					 | 
				
			||||||
  garage: 'rtsp://frigate:8554/garage'
 | 
					  garage: 'rtsp://frigate:8554/garage'
 | 
				
			||||||
ping_interval_s: 60
 | 
					ping_interval_s: 60
 | 
				
			||||||
ping_timeout_s: 30
 | 
					ping_timeout_s: 30
 | 
				
			||||||
consecutive_down_threshold: 3
 | 
					consecutive_down_threshold: 3
 | 
				
			||||||
 | 
					notification_repeat_threshold: 30
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,6 @@ func main() {
 | 
				
			||||||
	var pingInterval = time.Duration(uptimeConfig.PingIntervalSeconds * int64(time.Second))
 | 
						var pingInterval = time.Duration(uptimeConfig.PingIntervalSeconds * int64(time.Second))
 | 
				
			||||||
	var pingTimeout = time.Duration(uptimeConfig.PingTimeoutSeconds * int64(time.Second))
 | 
						var pingTimeout = time.Duration(uptimeConfig.PingTimeoutSeconds * int64(time.Second))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var cameraMonitor CameraMonitor = NewCameraMonitor(pingInterval, pingTimeout, uptimeConfig.ConsecutiveDownThreshold, notify.NewNtfyNotifier())
 | 
						var cameraMonitor CameraMonitor = NewCameraMonitor(pingInterval, pingTimeout, uptimeConfig.ConsecutiveDownThreshold, uptimeConfig.NotificationRepeatThreshold, notify.NewNtfyNotifier())
 | 
				
			||||||
	cameraMonitor.Run(uptimeConfig.Cameras)
 | 
						cameraMonitor.Run(uptimeConfig.Cameras)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,44 +13,40 @@ import (
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type CameraMonitor struct {
 | 
					type CameraMonitor struct {
 | 
				
			||||||
	pingInterval             time.Duration
 | 
						pingInterval                time.Duration
 | 
				
			||||||
	pingTimeout              time.Duration
 | 
						pingTimeout                 time.Duration
 | 
				
			||||||
	consecutiveDownThreshold int
 | 
						consecutiveDownThreshold    int
 | 
				
			||||||
	downtime                 map[string]int
 | 
						notificationRepeatThreshold int
 | 
				
			||||||
 | 
						downtime                    map[string]int
 | 
				
			||||||
	notify.Notifier
 | 
						notify.Notifier
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewCameraMonitor(pingInterval time.Duration, pingTimeout time.Duration, consecutiveDownThreshold int, notifier notify.Notifier) CameraMonitor {
 | 
					func NewCameraMonitor(pingInterval time.Duration, pingTimeout time.Duration, consecutiveDownThreshold int, notificationRepeatThreshold int, notifier notify.Notifier) CameraMonitor {
 | 
				
			||||||
	return CameraMonitor{
 | 
						return CameraMonitor{
 | 
				
			||||||
		pingInterval:             pingInterval,
 | 
							pingInterval:                pingInterval,
 | 
				
			||||||
		pingTimeout:              pingTimeout,
 | 
							pingTimeout:                 pingTimeout,
 | 
				
			||||||
		consecutiveDownThreshold: consecutiveDownThreshold,
 | 
							consecutiveDownThreshold:    consecutiveDownThreshold,
 | 
				
			||||||
		downtime:                 make(map[string]int),
 | 
							notificationRepeatThreshold: notificationRepeatThreshold,
 | 
				
			||||||
		Notifier:                 notifier,
 | 
							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) {
 | 
					func (c CameraMonitor) onCameraPingResult(camera string, online bool) {
 | 
				
			||||||
	if online {
 | 
						if online {
 | 
				
			||||||
		if c.downtime[camera] >= c.consecutiveDownThreshold {
 | 
							if c.downtime[camera] >= c.consecutiveDownThreshold {
 | 
				
			||||||
			c.onCameraUp(camera)
 | 
								c.SendNotification(camera, true)
 | 
				
			||||||
 | 
								slog.Info(fmt.Sprintf("%s camera is back online!", camera))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		c.downtime[camera] = 0
 | 
							c.downtime[camera] = 0
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		c.downtime[camera] += 1
 | 
							c.downtime[camera] += 1
 | 
				
			||||||
		if c.downtime[camera] == c.consecutiveDownThreshold {
 | 
							if c.downtime[camera] == c.consecutiveDownThreshold {
 | 
				
			||||||
			c.onCameraDown(camera)
 | 
								c.SendNotification(camera, false)
 | 
				
			||||||
 | 
								slog.Info(fmt.Sprintf("%s camera is offline!", camera))
 | 
				
			||||||
 | 
							} else if c.notificationRepeatThreshold >= c.consecutiveDownThreshold && c.downtime[camera]%c.notificationRepeatThreshold == c.consecutiveDownThreshold {
 | 
				
			||||||
 | 
								c.SendNotification(camera, false)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,14 +5,13 @@ import (
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
type Notifier interface {
 | 
					type Notifier interface {
 | 
				
			||||||
	SendNotification(camera string, online bool)
 | 
						SendNotification(camera string, online bool)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func createMessage(camera string, online bool) string {
 | 
					func createMessage(camera string, online bool) string {
 | 
				
			||||||
	var msgTemplates = map[bool]string{
 | 
						var msgTemplates = map[bool]string{
 | 
				
			||||||
		true: "%s camera is back online!",
 | 
							true:  "%s camera is back online!",
 | 
				
			||||||
		false: "%s camera is offline!",
 | 
							false: "%s camera is offline!",
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,15 +1,14 @@
 | 
				
			||||||
package notify
 | 
					package notify
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"crypto/tls"
 | 
					 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
 | 
						"crypto/tls"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"log/slog"
 | 
						"log/slog"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
type NtfyNotifier struct {
 | 
					type NtfyNotifier struct {
 | 
				
			||||||
	client *http.Client
 | 
						client *http.Client
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -26,21 +25,21 @@ func NewNtfyNotifier() NtfyNotifier {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (n NtfyNotifier) SendNotification(camera string, online bool) {
 | 
					func (n NtfyNotifier) SendNotification(camera string, online bool) {
 | 
				
			||||||
	type ntfyJson struct {
 | 
						type ntfyJson struct {
 | 
				
			||||||
		Topic string `json:"topic"`
 | 
							Topic    string `json:"topic"`
 | 
				
			||||||
		Title string `json:"title"`
 | 
							Title    string `json:"title"`
 | 
				
			||||||
		Message string `json:"message"`
 | 
							Message  string `json:"message"`
 | 
				
			||||||
		Priority int `json:"priority"`
 | 
							Priority int    `json:"priority"`
 | 
				
			||||||
		Click string `json:"click"`
 | 
							Click    string `json:"click"`
 | 
				
			||||||
		Icon string `json:"icon"`
 | 
							Icon     string `json:"icon"`
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var reqJson, jsonErr = json.Marshal(ntfyJson{
 | 
						var reqJson, jsonErr = json.Marshal(ntfyJson{
 | 
				
			||||||
		Topic: "frigate_camera_uptime",
 | 
							Topic:    "frigate_camera_uptime",
 | 
				
			||||||
		Title: "Frigate",
 | 
							Title:    "Frigate",
 | 
				
			||||||
		Message: createMessage(camera, online),
 | 
							Message:  createMessage(camera, online),
 | 
				
			||||||
		Priority: 3,
 | 
							Priority: 3,
 | 
				
			||||||
		Click: fmt.Sprintf("https://frigate.homelab.net/cameras/%s", camera),
 | 
							Click:    fmt.Sprintf("https://frigate.homelab.net/cameras/%s", camera),
 | 
				
			||||||
		Icon: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/frigate.png",
 | 
							Icon:     "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/frigate.png",
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if jsonErr != nil {
 | 
						if jsonErr != nil {
 | 
				
			||||||
		slog.Error("Failed to construct JSON request body", "error", jsonErr)
 | 
							slog.Error("Failed to construct JSON request body", "error", jsonErr)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,6 @@ package ping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "os/exec"
 | 
					import "os/exec"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
func IPPing(host string) bool {
 | 
					func IPPing(host string) bool {
 | 
				
			||||||
	var cmd = exec.Command("ping", "-w", "3", "-c", "1", host)
 | 
						var cmd = exec.Command("ping", "-w", "3", "-c", "1", host)
 | 
				
			||||||
	var err = cmd.Run()
 | 
						var err = cmd.Run()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,6 @@ package ping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "os/exec"
 | 
					import "os/exec"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
func VideoPing(url string) bool {
 | 
					func VideoPing(url string) bool {
 | 
				
			||||||
	var cmd = exec.Command("ffprobe", "-rtsp_transport", "tcp", "-i", url, "-timeout", "10000000")
 | 
						var cmd = exec.Command("ffprobe", "-rtsp_transport", "tcp", "-i", url, "-timeout", "10000000")
 | 
				
			||||||
	var err = cmd.Run()
 | 
						var err = cmd.Run()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue