package sensor import ( "crypto/rand" "log/slog" "time" mqtt "github.com/eclipse/paho.mqtt.golang" ) // MQTTConfig selects the broker, sensor topic, and optional broker credentials. type MQTTConfig struct { Broker string Topic string Username string Password string } // StartMQTT connects in the background, retrying initial failures and reconnecting // after outages. Every connection subscribes again because sessions are clean. // The caller must Disconnect the returned client during shutdown. func StartMQTT(config MQTTConfig, sampler *Sampler, logger *slog.Logger) mqtt.Client { options := mqtt.NewClientOptions(). AddBroker(config.Broker). SetClientID("laundry-" + rand.Text()[:12]). SetUsername(config.Username). SetPassword(config.Password). SetAutoReconnect(true). SetConnectRetry(true). SetConnectRetryInterval(5 * time.Second). SetConnectTimeout(5 * time.Second) options.SetOnConnectHandler(func(client mqtt.Client) { token := client.Subscribe(config.Topic, 0, func(_ mqtt.Client, message mqtt.Message) { if err := sampler.Receive(message.Payload()); err != nil { logger.Warn("Ignoring invalid sensor payload", "error", err) } }) if !token.WaitTimeout(10 * time.Second) { logger.Error("MQTT subscription timed out") } else if err := token.Error(); err != nil { logger.Error("MQTT subscription failed", "error", err) } else { logger.Info("MQTT sensor subscription ready", "topic", config.Topic) } }) options.SetConnectionLostHandler(func(_ mqtt.Client, err error) { logger.Warn("MQTT connection lost; reconnecting", "error", err) }) client := mqtt.NewClient(options) client.Connect() return client }