Skip to content

Instantly share code, notes, and snippets.

@chazcheadle
Last active September 29, 2022 22:57
Show Gist options
  • Select an option

  • Save chazcheadle/b6d995b5e79c100a2d1b7e1646291fd2 to your computer and use it in GitHub Desktop.

Select an option

Save chazcheadle/b6d995b5e79c100a2d1b7e1646291fd2 to your computer and use it in GitHub Desktop.
Golang/MQTT service to monitor webcam snapshots and send status
package main
import (
"encoding/json"
"io"
"io/ioutil"
"os"
"sort"
"time"
log "github.com/Sirupsen/logrus"
MQTT "github.com/eclipse/paho.mqtt.golang"
)
type Cameras struct {
Cameras []Camera
}
type Camera struct {
CameraID string `json:"camera_id"`
CameraName string `json:"camera_name"`
ImageDir string `json:"image_dir"`
LastImage string `json:"last_image"`
Online bool `json:"online"`
StatusCode string `json:"status_code"`
Timestamp time.Time `json:"timestamp"`
ErrorCount int `json:"error_count"`
}
type Data struct {
Online bool
Timestamp time.Time
}
// Create struct to be used by our custom sort function.
type ByTime []os.FileInfo
// Len, Swap and Less functions for sorting files by ModTime.
func (f ByTime) Len() int { return len(f) }
func (f ByTime) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
func (f ByTime) Less(i, j int) bool { return f[i].ModTime().After(f[j].ModTime()) }
var (
image_dirs = []string{"/tmp/hab_camera-1", "/tmp/hab_camera-2"}
mqttBroker = "tcp://HOSTNAME:8883"
mqttClientId = "Camera-Monitor"
errorThreshold = 2
snapshotCount = 0
)
func copyFile(src, dst string) (err error) {
in, err := os.Open(src)
if err != nil {
return
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return
}
defer func() {
cerr := out.Close()
if err == nil {
err = cerr
}
}()
if _, err = io.Copy(out, in); err != nil {
return
}
err = out.Sync()
return
}
func main() {
cameras := Cameras{
Cameras: []Camera{{"hab_camera-1", "Exterior (South)", "/tmp/hab_camera-1", "", false, "", time.Now(), 0}, {"hab_camera-2", "Exterior IR (South)", "/tmp/hab_camera-2", "", false, "", time.Now(), 0}},
}
opts := MQTT.NewClientOptions()
opts.AddBroker(mqttBroker)
opts.SetClientID(mqttClientId)
c := MQTT.NewClient(opts)
if token := c.Connect(); token.Wait() && token.Error() != nil {
log.Warn("Could not connect to: ", mqttBroker)
}
for index, cam := range cameras.Cameras {
ticker := time.NewTicker(time.Millisecond * 3000)
go func(index int, cam Camera) {
for timestamp := range ticker.C {
files, err := ioutil.ReadDir(cam.ImageDir)
if err != nil {
log.Fatal(err)
}
// Sort files by time if there happens to be more than one.
if len(files) > 1 {
sort.Sort(ByTime(files))
}
if len(files) > 0 {
// Check if the last image has been updated.
if cameras.Cameras[index].LastImage == files[0].Name() {
// Allow a threshold for unchanged images due to camera software performance.
if cameras.Cameras[index].ErrorCount >= errorThreshold {
cameras.Cameras[index].Online = false
cameras.Cameras[index].StatusCode = "No new image found."
} else {
cameras.Cameras[index].ErrorCount++
}
} else {
cameras.Cameras[index].Online = true
cameras.Cameras[index].ErrorCount = 0
cameras.Cameras[index].StatusCode = ""
if snapshotCount == 0 || snapshotCount > 4 {
cerr := copyFile(cam.ImageDir+"/"+files[0].Name(), cam.ImageDir+"/out.jpg")
if cerr != nil {
log.Warn("File copy failed")
log.Warn(cerr)
}
snapshotCount = 0
}
snapshotCount++
}
cameras.Cameras[index].LastImage = files[0].Name()
cameras.Cameras[index].Timestamp = timestamp
}
// Build message data packet and json.Marshal
var data = &Data{}
data.Timestamp = timestamp
data.Online = cameras.Cameras[index].Online
messageData, _ := json.Marshal(data)
// Send MQTT message
token := c.Publish("test/cameras/"+cam.CameraID, 0, true, messageData)
token.Wait()
}
}(index, cam)
}
select {}
}
@chazcheadle
Copy link
Author

TODO: Separate global variables for errorCount, snapshotCount etc. as the go routines are sharing them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment