mirror of
https://github.com/jwetzell/showbridge-go.git
synced 2026-07-26 09:58:40 +00:00
195 lines
4.4 KiB
Go
195 lines
4.4 KiB
Go
package module
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"sync"
|
|
|
|
mqtt "github.com/eclipse/paho.mqtt.golang"
|
|
"github.com/google/jsonschema-go/jsonschema"
|
|
"github.com/jwetzell/showbridge-go/internal/common"
|
|
"github.com/jwetzell/showbridge-go/internal/config"
|
|
)
|
|
|
|
func init() {
|
|
RegisterModule(ModuleRegistration{
|
|
Type: "mqtt.client",
|
|
Title: "MQTT Client",
|
|
ParamsSchema: &jsonschema.Schema{
|
|
Type: "object",
|
|
Properties: map[string]*jsonschema.Schema{
|
|
"broker": {
|
|
Title: "Broker URL",
|
|
Type: "string",
|
|
},
|
|
"topic": {
|
|
Title: "Topic",
|
|
Type: "string",
|
|
},
|
|
"clientId": {
|
|
Title: "Client ID",
|
|
Type: "string",
|
|
},
|
|
"qos": {
|
|
Title: "QoS",
|
|
Type: "integer",
|
|
Minimum: jsonschema.Ptr[float64](0),
|
|
Maximum: jsonschema.Ptr[float64](2),
|
|
Default: json.RawMessage(`0`),
|
|
},
|
|
"retained": {
|
|
Title: "Retained",
|
|
Type: "boolean",
|
|
Default: json.RawMessage(`false`),
|
|
},
|
|
},
|
|
Required: []string{"broker", "topic", "clientId"},
|
|
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
|
|
},
|
|
New: func(moduleConfig config.ModuleConfig) (common.Module, error) {
|
|
params := moduleConfig.Params
|
|
brokerString, err := params.GetString("broker")
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("mqtt.client broker error: %w", err)
|
|
}
|
|
|
|
topicString, err := params.GetString("topic")
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("mqtt.client topic error: %w", err)
|
|
}
|
|
|
|
clientIdString, err := params.GetString("clientId")
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("mqtt.client clientId error: %w", err)
|
|
}
|
|
|
|
qosString, err := params.GetInt("qos")
|
|
|
|
if err != nil {
|
|
if errors.Is(err, config.ErrParamNotFound) {
|
|
qosString = 0
|
|
} else {
|
|
return nil, fmt.Errorf("mqtt.client qos error: %w", err)
|
|
}
|
|
}
|
|
|
|
retainedBool, err := params.GetBool("retained")
|
|
|
|
if err != nil {
|
|
if errors.Is(err, config.ErrParamNotFound) {
|
|
retainedBool = false
|
|
} else {
|
|
return nil, fmt.Errorf("mqtt.client retained error: %w", err)
|
|
}
|
|
}
|
|
|
|
return &MQTTClient{config: moduleConfig, Broker: brokerString, Topic: topicString, ClientID: clientIdString, QoS: byte(qosString), Retained: retainedBool, logger: CreateLogger(moduleConfig)}, nil
|
|
},
|
|
})
|
|
}
|
|
|
|
type MQTTClient struct {
|
|
config config.ModuleConfig
|
|
ctx context.Context
|
|
inputHandler common.InputHandler
|
|
Broker string
|
|
ClientID string
|
|
Topic string
|
|
QoS byte
|
|
Retained bool
|
|
client mqtt.Client
|
|
logger *slog.Logger
|
|
cancel context.CancelFunc
|
|
clientMu sync.Mutex
|
|
}
|
|
|
|
func (mc *MQTTClient) Id() string {
|
|
return mc.config.Id
|
|
}
|
|
|
|
func (mc *MQTTClient) Type() string {
|
|
return mc.config.Type
|
|
}
|
|
|
|
func (mc *MQTTClient) Start(ctx context.Context, inputHandler common.InputHandler) error {
|
|
mc.logger.Debug("running")
|
|
mc.inputHandler = inputHandler
|
|
moduleContext, cancel := context.WithCancel(ctx)
|
|
mc.ctx = moduleContext
|
|
mc.cancel = cancel
|
|
|
|
opts := mqtt.NewClientOptions()
|
|
opts.AddBroker(mc.Broker)
|
|
opts.SetClientID(mc.ClientID)
|
|
opts.SetAutoReconnect(true)
|
|
opts.SetCleanSession(false)
|
|
|
|
opts.OnConnect = func(c mqtt.Client) {
|
|
token := mc.client.Subscribe(mc.Topic, 1, func(c mqtt.Client, m mqtt.Message) {
|
|
if mc.inputHandler != nil {
|
|
mc.inputHandler(mc.ctx, mc.Id(), m)
|
|
}
|
|
})
|
|
token.Wait()
|
|
}
|
|
|
|
mc.clientMu.Lock()
|
|
mc.client = mqtt.NewClient(opts)
|
|
|
|
token := mc.client.Connect()
|
|
|
|
token.Wait()
|
|
err := token.Error()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mc.clientMu.Unlock()
|
|
|
|
<-mc.ctx.Done()
|
|
mc.logger.Debug("done")
|
|
return nil
|
|
}
|
|
|
|
func (mc *MQTTClient) Publish(ctx context.Context, topic string, payload any) error {
|
|
payloadBytes, ok := common.GetAnyAsByteSlice(payload)
|
|
|
|
if !ok {
|
|
payloadString, ok := common.GetAnyAs[string](payload)
|
|
if !ok {
|
|
return errors.New("mqtt.client is only able to publish bytes or string")
|
|
}
|
|
payloadBytes = []byte(payloadString)
|
|
}
|
|
|
|
if mc.client == nil {
|
|
return errors.New("mqtt.client client is not setup")
|
|
}
|
|
|
|
if !mc.client.IsConnected() {
|
|
return errors.New("mqtt.client is not connected")
|
|
}
|
|
|
|
token := mc.client.Publish(topic, mc.QoS, mc.Retained, payloadBytes)
|
|
|
|
token.Wait()
|
|
|
|
return token.Error()
|
|
}
|
|
|
|
func (mc *MQTTClient) Stop() {
|
|
if mc.cancel != nil {
|
|
mc.cancel()
|
|
}
|
|
mc.clientMu.Lock()
|
|
defer mc.clientMu.Unlock()
|
|
if mc.client != nil {
|
|
mc.client.Disconnect(250)
|
|
}
|
|
}
|