- New StreamManager for dynamic RTSP/FLV stream lifecycle - Dynamic worker scaling for inference - Device UUID generation and persistence - Telegraf config and NPU monitoring scripts - .gitignore for build artifacts
124 lines
3.0 KiB
Go
124 lines
3.0 KiB
Go
package control
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
mqtt "github.com/eclipse/paho.mqtt.golang"
|
|
"tianyan-edge/internal/config"
|
|
)
|
|
|
|
type MqttManager struct {
|
|
client mqtt.Client
|
|
cfg *config.Config
|
|
updates chan *config.Config // Channel to broadcast config updates
|
|
}
|
|
|
|
func NewMqttManager(cfg *config.Config) *MqttManager {
|
|
return &MqttManager{
|
|
cfg: cfg,
|
|
updates: make(chan *config.Config, 10),
|
|
}
|
|
}
|
|
|
|
func (m *MqttManager) Run(ctx context.Context) {
|
|
if m.cfg.MqttBroker == "" {
|
|
log.Println("mqtt: no broker configured, skipping")
|
|
return
|
|
}
|
|
|
|
opts := mqtt.NewClientOptions()
|
|
opts.AddBroker(m.cfg.MqttBroker)
|
|
opts.SetClientID(m.cfg.GetDeviceIdentity())
|
|
opts.SetUsername(m.cfg.MqttUser)
|
|
opts.SetPassword(m.cfg.MqttPass)
|
|
opts.SetAutoReconnect(true)
|
|
opts.SetMaxReconnectInterval(1 * time.Minute)
|
|
|
|
// LWT
|
|
willTopic := fmt.Sprintf("tianyan/edge/%s/status", m.cfg.GetDeviceIdentity())
|
|
opts.SetWill(willTopic, "offline", 1, true)
|
|
|
|
opts.SetOnConnectHandler(func(c mqtt.Client) {
|
|
log.Println("mqtt: connected to broker")
|
|
c.Publish(willTopic, 1, true, "online")
|
|
configTopic := fmt.Sprintf("tianyan/edge/%s/config", m.cfg.GetDeviceIdentity())
|
|
token := c.Subscribe(configTopic, 1, m.handleConfigUpdate)
|
|
token.Wait()
|
|
if token.Error() != nil {
|
|
log.Printf("mqtt: subscribe failed: %v", token.Error())
|
|
} else {
|
|
log.Printf("mqtt: subscribed to %s", configTopic)
|
|
}
|
|
})
|
|
|
|
m.client = mqtt.NewClient(opts)
|
|
if token := m.client.Connect(); token.Wait() && token.Error() != nil {
|
|
log.Printf("mqtt: connect error: %v", token.Error())
|
|
}
|
|
|
|
<-ctx.Done()
|
|
m.client.Disconnect(1000)
|
|
log.Println("mqtt: disconnected")
|
|
}
|
|
|
|
func (m *MqttManager) handleConfigUpdate(c mqtt.Client, msg mqtt.Message) {
|
|
log.Printf("mqtt: received config update on %s", msg.Topic())
|
|
var newCfg map[string]interface{}
|
|
if err := json.Unmarshal(msg.Payload(), &newCfg); err != nil {
|
|
log.Printf("mqtt: config parse error: %v", err)
|
|
return
|
|
}
|
|
|
|
// Update local config struct
|
|
if v, ok := newCfg["infer_fps"].(float64); ok {
|
|
m.cfg.InferFPS = int(v)
|
|
}
|
|
if v, ok := newCfg["conf_threshold"].(float64); ok {
|
|
m.cfg.ConfThreshold = v
|
|
}
|
|
if v, ok := newCfg["infer_workers"].(float64); ok {
|
|
m.cfg.InferWorkers = int(v)
|
|
}
|
|
if v, ok := newCfg["rtsp_urls"]; ok {
|
|
if urls, ok := v.([]interface{}); ok {
|
|
var s []string
|
|
for _, u := range urls {
|
|
if str, ok := u.(string); ok {
|
|
s = append(s, str)
|
|
}
|
|
}
|
|
m.cfg.RTSPURLs = s
|
|
}
|
|
}
|
|
|
|
// Save to disk
|
|
if err := m.cfg.Save(); err != nil {
|
|
log.Printf("mqtt: failed to save config: %v", err)
|
|
} else {
|
|
log.Println("mqtt: config saved to disk")
|
|
}
|
|
|
|
// Broadcast update to other components
|
|
select {
|
|
case m.updates <- m.cfg:
|
|
default:
|
|
log.Println("mqtt: update channel full, dropping update")
|
|
}
|
|
}
|
|
|
|
func (m *MqttManager) GetUpdates() <-chan *config.Config {
|
|
return m.updates
|
|
}
|
|
|
|
func (m *MqttManager) Publish(topic string, payload interface{}) {
|
|
if m.client == nil || !m.client.IsConnected() {
|
|
return
|
|
}
|
|
data, _ := json.Marshal(payload)
|
|
m.client.Publish(topic, 1, false, data)
|
|
}
|