package main import ( "context" "encoding/json" "log" "os" "os/signal" "syscall" "time" "example.com/mqtt5-learning/internal/config" mqtt "example.com/mqtt5-learning/internal/mqtt" ) func main() { // Convert a returned application error into a visible nonzero process exit. if err := run(); err != nil { log.Print(err) os.Exit(1) } } func run() error { // Load settings and bind cancellation to Ctrl+C or the container stop signal. cfg, err := config.Load(os.Args[1:]) if err != nil { return err } ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() // Build this device's topic namespace and bounded receive queue. base := "lab/v1/devices/" + cfg.MQTT.DeviceID inbox := make(chan mqtt.Message, 16) manager, err := mqtt.Start(ctx, cfg, []string{base + "/commands"}, inbox) if err != nil { return err } defer mqtt.Stop(manager) // Heartbeats are periodic; command handling remains active between ticks. ticker := time.NewTicker(3 * time.Second) defer ticker.Stop() for { // Handle cancellation, telemetry publication, and inbound commands together. select { case <-ctx.Done(): return nil case <-ticker.C: payload, err := json.Marshal(struct { DeviceID string `json:"device_id"` Time string `json:"time"` }{cfg.MQTT.DeviceID, time.Now().UTC().Format(time.RFC3339)}) if err != nil { return err } if err := mqtt.Publish(ctx, manager, base+"/telemetry", payload); err != nil { log.Printf("heartbeat: %v", err) } case message := <-inbox: // Decode into a local command value only after rejecting retained messages. var command struct { ID string `json:"id"` Command string `json:"command"` } if message.Retain { log.Print("ignoring retained command") continue } if err := json.Unmarshal(message.Payload, &command); err != nil || command.ID == "" || command.Command != "ping" { log.Print("ignoring malformed or unsupported command") continue } // Echo the command ID so the backend can associate this pong with its ping. response, err := json.Marshal(struct { ID string `json:"id"` DeviceID string `json:"device_id"` Status string `json:"status"` }{command.ID, cfg.MQTT.DeviceID, "pong"}) if err != nil { return err } if err := mqtt.Publish(ctx, manager, base+"/responses", response); err != nil { log.Printf("response: %v", err) } } } }