Files
AI-tianyan/internal/control/mqtt_manager.go

124 lines
3.0 KiB
Go
Raw Normal View History

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)
}