Add MQTT client wrapper; wire startup publish; support env-config
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user