// Package mqttclient subscribes to device telemetry topics over MQTT. package mqttclient import ( "fmt" "log/slog" "time" mqtt "github.com/eclipse/paho.mqtt.golang" ) // Handler receives one message's raw payload and the time it arrived. type Handler func(payload []byte, receivedAt time.Time) type Config struct { BrokerURL string // e.g. tcp://mosquitto:1883 ClientID string Topic string // e.g. devices/+/telemetry QoS byte } type Subscriber struct { client mqtt.Client } // Connect dials the broker and (re-)subscribes to cfg.Topic on every // successful connection, so a dropped connection resubscribes automatically // once auto-reconnect succeeds. func Connect(cfg Config, handler Handler, logger *slog.Logger) (*Subscriber, error) { opts := mqtt.NewClientOptions(). AddBroker(cfg.BrokerURL). SetClientID(cfg.ClientID). SetAutoReconnect(true). SetConnectRetry(true). SetOnConnectHandler(func(c mqtt.Client) { token := c.Subscribe(cfg.Topic, cfg.QoS, func(_ mqtt.Client, msg mqtt.Message) { handler(msg.Payload(), time.Now()) }) token.Wait() if err := token.Error(); err != nil { logger.Error("mqtt subscribe failed", "topic", cfg.Topic, "error", err) } }). SetConnectionLostHandler(func(_ mqtt.Client, err error) { logger.Warn("mqtt connection lost", "error", err) }) client := mqtt.NewClient(opts) token := client.Connect() token.Wait() if err := token.Error(); err != nil { return nil, fmt.Errorf("connect to mqtt broker %s: %w", cfg.BrokerURL, err) } return &Subscriber{client: client}, nil } // Close disconnects, waiting up to quiesceMS for in-flight work to settle. func (s *Subscriber) Close() { s.client.Disconnect(250) }