Add MQTT client wrapper; wire startup publish; support env-config

This commit is contained in:
2025-12-28 15:02:52 +01:00
parent bfe8704f18
commit c866346c06
6 changed files with 220 additions and 4 deletions
+94
View File
@@ -0,0 +1,94 @@
package config
import (
"os"
"strconv"
"github.com/joho/godotenv"
"github.com/pelletier/go-toml/v2"
)
// Config holds application configuration loaded from TOML
type Config struct {
Server ServerConfig `toml:"server"`
MQTT MQTTConfig `toml:"mqtt"`
}
type ServerConfig struct {
Address string `toml:"address"`
Port int `toml:"port"`
JWTSecret string `toml:"jwt_secret"`
}
type MQTTConfig struct {
Broker string `toml:"broker"`
ClientID string `toml:"client_id"`
Username string `toml:"username"`
Password string `toml:"password"`
Topic string `toml:"topic"`
}
// Load loads configuration from the given path. If path is empty, it tries
// to load ./config.toml or falls back to environment defaults.
func Load(path string) (*Config, error) {
cfg := &Config{
Server: ServerConfig{
Address: "0.0.0.0",
Port: 8080,
JWTSecret: "secret",
},
MQTT: MQTTConfig{
Broker: "tcp://localhost:1883",
ClientID: "lambda-iot-core",
Topic: "lambda/iot",
},
}
// try to load TOML if provided or present
if path == "" {
path = "config.toml"
}
if _, err := os.Stat(path); err == nil {
f, err := os.ReadFile(path)
if err != nil {
return nil, err
}
if err := toml.Unmarshal(f, cfg); err != nil {
return nil, err
}
}
// load .env (if present) so env vars are populated for overrides
_ = godotenv.Load()
// environment overrides (common for Docker/.env)
if v := os.Getenv("SERVER_ADDRESS"); v != "" {
cfg.Server.Address = v
}
if v := os.Getenv("SERVER_PORT"); v != "" {
if p, err := strconv.Atoi(v); err == nil {
cfg.Server.Port = p
}
}
if v := os.Getenv("JWT_SECRET"); v != "" {
cfg.Server.JWTSecret = v
}
if v := os.Getenv("MQTT_BROKER"); v != "" {
cfg.MQTT.Broker = v
}
if v := os.Getenv("MQTT_CLIENT_ID"); v != "" {
cfg.MQTT.ClientID = v
}
if v := os.Getenv("MQTT_USERNAME"); v != "" {
cfg.MQTT.Username = v
}
if v := os.Getenv("MQTT_PASSWORD"); v != "" {
cfg.MQTT.Password = v
}
if v := os.Getenv("MQTT_TOPIC"); v != "" {
cfg.MQTT.Topic = v
}
return cfg, nil
}
+56
View File
@@ -0,0 +1,56 @@
package mqtt
import (
"fmt"
"time"
paho "github.com/eclipse/paho.mqtt.golang"
"git.piskot.si/SeminarM2/lambdaiot-core/internal/config"
)
type Client struct {
client paho.Client
}
func Connect(cfg config.MQTTConfig) (*Client, error) {
opts := paho.NewClientOptions()
opts.AddBroker(cfg.Broker)
if cfg.ClientID != "" {
opts.SetClientID(cfg.ClientID)
}
if cfg.Username != "" {
opts.SetUsername(cfg.Username)
opts.SetPassword(cfg.Password)
}
opts.SetAutoReconnect(true)
opts.SetConnectRetry(true)
opts.SetConnectTimeout(5 * time.Second)
c := paho.NewClient(opts)
token := c.Connect()
if ok := token.WaitTimeout(10 * time.Second); !ok {
return nil, fmt.Errorf("mqtt connect timeout")
}
if err := token.Error(); err != nil {
return nil, err
}
return &Client{client: c}, nil
}
func (c *Client) Publish(topic string, payload []byte) error {
if c == nil || c.client == nil {
return fmt.Errorf("mqtt client not connected")
}
token := c.client.Publish(topic, 0, false, payload)
if ok := token.WaitTimeout(5 * time.Second); !ok {
return fmt.Errorf("publish timeout")
}
return token.Error()
}
func (c *Client) Close() {
if c == nil || c.client == nil {
return
}
c.client.Disconnect(250)
}