88 lines
2.4 KiB
Go
88 lines
2.4 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|
|
}
|