github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/plugins/mqtt_bridge/mqtt_bridge.go (about) 1 // This file is part of the Smart Home 2 // Program complex distribution https://github.com/e154/smart-home 3 // Copyright (C) 2023, Filippov Alex 4 // 5 // This library is free software: you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 3 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Library General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library. If not, see 17 // <https://www.gnu.org/licenses/>. 18 19 package mqtt_bridge 20 21 import ( 22 "context" 23 "time" 24 25 "github.com/e154/smart-home/common" 26 "github.com/e154/smart-home/system/supervisor" 27 28 MQTT "github.com/eclipse/paho.mqtt.golang" 29 "github.com/google/uuid" 30 "go.uber.org/atomic" 31 32 "github.com/e154/smart-home/system/mqtt" 33 ) 34 35 type MqttBridge struct { 36 cfg *Config 37 client MQTT.Client 38 server mqtt.MqttServ 39 serverClient mqtt.MqttCli 40 isStarted *atomic.Bool 41 actor *Actor 42 } 43 44 func NewMqttBridge(cfg *Config, server mqtt.MqttServ, actor *Actor) (bridge *MqttBridge, err error) { 45 bridge = &MqttBridge{ 46 cfg: cfg, 47 server: server, 48 isStarted: atomic.NewBool(false), 49 serverClient: server.NewClient(uuid.NewString()), 50 actor: actor, 51 } 52 return 53 } 54 55 func (m *MqttBridge) Start(ctx context.Context) (err error) { 56 if m.isStarted.Load() { 57 return 58 } 59 m.isStarted.Store(true) 60 defer func() { 61 if err != nil { 62 m.isStarted.Store(false) 63 } 64 }() 65 66 opts := MQTT.NewClientOptions(). 67 AddBroker(m.cfg.Broker). 68 SetClientID(m.cfg.ClientID). 69 SetKeepAlive(time.Duration(m.cfg.KeepAlive) * time.Second). 70 SetPingTimeout(time.Duration(m.cfg.PingTimeout) * time.Second). 71 SetConnectTimeout(time.Duration(m.cfg.ConnectTimeout) * time.Second). 72 SetMaxReconnectInterval(time.Minute). 73 SetAutoReconnect(true). 74 SetCleanSession(m.cfg.CleanSession). 75 SetOnConnectHandler(m.onConnect). 76 SetConnectionLostHandler(m.onConnectionLostHandler). 77 SetUsername(m.cfg.Username). 78 SetPassword(m.cfg.Password) 79 m.client = MQTT.NewClient(opts) 80 if token := m.client.Connect(); token.Wait() && token.Error() != nil { 81 log.Error(token.Error().Error()) 82 } 83 return 84 } 85 86 func (m *MqttBridge) Shutdown(ctx context.Context) (err error) { 87 if !m.isStarted.Load() { 88 return 89 } 90 m.isStarted.Store(false) 91 if m.client != nil { 92 m.client.Disconnect(250) 93 } 94 return 95 } 96 97 func (m *MqttBridge) onConnect(client MQTT.Client) { 98 99 switch m.cfg.Direction { 100 case DirectionBoth: 101 m.directionBoth(client) 102 case DirectionIn: 103 m.directionIn(client) 104 case DirectionOut: 105 m.directionOut(client) 106 default: 107 log.Debugf("unknown direction '%s'", m.cfg.Direction) 108 } 109 110 m.actor.SetState(supervisor.EntityStateParams{ 111 NewState: common.String(AttrConnected), 112 StorageSave: true, 113 }) 114 115 log.Debug("connected ...") 116 } 117 118 func (m *MqttBridge) onConnectionLostHandler(client MQTT.Client, e error) { 119 120 _ = m.actor.SetState(supervisor.EntityStateParams{ 121 NewState: common.String(AttrOffline), 122 StorageSave: true, 123 }) 124 125 //if e != nil { 126 // log.Debugf("connection lost... %s", e.Error()) 127 //} else { 128 // log.Debug("connection lost...") 129 //} 130 131 for _, topic := range m.cfg.Topics { 132 if token := m.client.Unsubscribe(topic); token.Error() != nil { 133 log.Error(token.Error().Error()) 134 } 135 } 136 m.serverClient.UnsubscribeAll() 137 } 138 139 func (m *MqttBridge) directionBoth(client MQTT.Client) { 140 141 for _, topic := range m.cfg.Topics { 142 log.Debugf("subscribe: %s", topic) 143 if token := client.Subscribe(topic, m.cfg.Qos, m.clientMessageHandler); token.Wait() && token.Error() != nil { 144 log.Error(token.Error().Error()) 145 } 146 147 if err := m.serverClient.Subscribe(topic, m.serverMessageHandler); err != nil { 148 log.Error(err.Error()) 149 } 150 } 151 } 152 153 func (m *MqttBridge) directionIn(client MQTT.Client) { 154 155 for _, topic := range m.cfg.Topics { 156 //log.Debugf("subscribe: %s", topic) 157 if token := client.Subscribe(topic, m.cfg.Qos, m.clientMessageHandler); token.Wait() && token.Error() != nil { 158 log.Error(token.Error().Error()) 159 } 160 } 161 } 162 163 func (m *MqttBridge) directionOut(client MQTT.Client) { 164 165 var err error 166 for _, topic := range m.cfg.Topics { 167 if err = m.serverClient.Subscribe(topic, m.serverMessageHandler); err != nil { 168 log.Error(err.Error()) 169 } 170 } 171 } 172 173 func (m *MqttBridge) serverMessageHandler(client mqtt.MqttCli, message mqtt.Message) { 174 m.client.Publish(message.Topic, message.Qos, message.Retained, message.Payload) 175 } 176 177 func (m *MqttBridge) clientMessageHandler(_ MQTT.Client, message MQTT.Message) { 178 if err := m.server.Publish(message.Topic(), message.Payload(), message.Qos(), message.Retained()); err != nil { 179 log.Error(err.Error()) 180 } 181 }