github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/mqtt_client/mqtt_client.go (about) 1 // This file is part of the Smart Home 2 // Program complex distribution https://github.com/e154/smart-home 3 // Copyright (C) 2016-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_client 20 21 import ( 22 "fmt" 23 "strings" 24 "sync" 25 "time" 26 27 "github.com/e154/smart-home/common/apperr" 28 29 "github.com/e154/smart-home/common/logger" 30 31 MQTT "github.com/eclipse/paho.mqtt.golang" 32 "github.com/pkg/errors" 33 ) 34 35 var ( 36 log = logger.MustGetLogger("mqtt_client") 37 ) 38 39 // Client ... 40 type Client struct { 41 cfg *Config 42 mx sync.Mutex 43 client MQTT.Client 44 subscribes map[string]Subscribe 45 } 46 47 // NewClient ... 48 func NewClient(cfg *Config) (client *Client, err error) { 49 50 log.Infof("new queue client(%s) uri(%s)", cfg.ClientID, cfg.Broker) 51 52 client = &Client{ 53 cfg: cfg, 54 subscribes: make(map[string]Subscribe), 55 } 56 57 opts := MQTT.NewClientOptions(). 58 AddBroker(cfg.Broker). 59 SetClientID(cfg.ClientID). 60 SetKeepAlive(time.Duration(cfg.KeepAlive) * time.Second). 61 SetPingTimeout(time.Duration(cfg.PingTimeout) * time.Second). 62 SetConnectTimeout(time.Duration(cfg.ConnectTimeout) * time.Second). 63 SetCleanSession(cfg.CleanSession). 64 SetOnConnectHandler(client.onConnect). 65 SetConnectionLostHandler(client.onConnectionLostHandler) 66 67 if cfg.Username != "" { 68 opts.SetUsername(cfg.Username) 69 } 70 71 if cfg.Password != "" { 72 opts.SetPassword(cfg.Password) 73 } 74 75 client.client = MQTT.NewClient(opts) 76 77 return 78 } 79 80 // Connect ... 81 func (c *Client) Connect() (err error) { 82 83 c.mx.Lock() 84 defer c.mx.Unlock() 85 86 log.Infof("Connect to server %s", c.cfg.Broker) 87 88 if token := c.client.Connect(); token.Wait() && token.Error() != nil { 89 log.Error(token.Error().Error()) 90 err = token.Error() 91 } 92 93 return 94 } 95 96 // Disconnect ... 97 func (c *Client) Disconnect() { 98 99 c.mx.Lock() 100 defer c.mx.Unlock() 101 102 if c.client == nil { 103 return 104 } 105 106 c.unsubscribeAll() 107 108 c.mx.Lock() 109 c.client.Disconnect(250) 110 //c.client = nil 111 } 112 113 // Subscribe ... 114 func (c *Client) Subscribe(topic string, qos byte, callback MQTT.MessageHandler) (err error) { 115 116 if topic == "" { 117 err = errors.Wrap(apperr.ErrInternal, "zero topic") 118 return 119 } 120 121 c.mx.Lock() 122 defer c.mx.Unlock() 123 124 if _, ok := c.subscribes[topic]; !ok { 125 c.subscribes[topic] = Subscribe{ 126 Qos: qos, 127 Callback: callback, 128 } 129 } else { 130 err = errors.Wrap(apperr.ErrInternal, fmt.Sprintf("topic %s exist", topic)) 131 return 132 } 133 134 if token := c.client.Subscribe(topic, qos, callback); token.Wait() && token.Error() != nil { 135 err = token.Error() 136 } 137 return 138 } 139 140 // Unsubscribe ... 141 func (c *Client) Unsubscribe(topic string) (err error) { 142 143 c.mx.Lock() 144 defer c.mx.Unlock() 145 146 if token := c.client.Unsubscribe(topic); token.Wait() && token.Error() != nil { 147 log.Error(token.Error().Error()) 148 return token.Error() 149 } 150 return 151 } 152 153 // unsubscribeAll ... 154 func (c *Client) unsubscribeAll() { 155 c.mx.Lock() 156 defer c.mx.Unlock() 157 158 for topic := range c.subscribes { 159 if token := c.client.Unsubscribe(topic); token.Error() != nil { 160 log.Error(token.Error().Error()) 161 } 162 delete(c.subscribes, topic) 163 } 164 } 165 166 // Publish ... 167 func (c *Client) Publish(topic string, payload interface{}) (err error) { 168 c.mx.Lock() 169 defer c.mx.Unlock() 170 171 if c.client != nil && (c.client.IsConnected()) { 172 c.client.Publish(topic, c.cfg.Qos, false, payload) 173 } 174 return 175 } 176 177 // IsConnected ... 178 func (c *Client) IsConnected() bool { 179 c.mx.Lock() 180 defer c.mx.Unlock() 181 182 return c.client.IsConnectionOpen() 183 } 184 185 func (c *Client) onConnectionLostHandler(client MQTT.Client, e error) { 186 187 c.mx.Lock() 188 defer c.mx.Unlock() 189 190 log.Debug("connection lost...") 191 192 for topic := range c.subscribes { 193 if token := c.client.Unsubscribe(topic); token.Error() != nil { 194 log.Error(token.Error().Error()) 195 } 196 } 197 } 198 199 func (c *Client) onConnect(client MQTT.Client) { 200 201 c.mx.Lock() 202 defer c.mx.Unlock() 203 204 log.Debug("connected...") 205 206 for topic, subscribe := range c.subscribes { 207 if token := c.client.Subscribe(topic, subscribe.Qos, subscribe.Callback); token.Wait() && token.Error() != nil { 208 log.Error(token.Error().Error()) 209 } 210 } 211 } 212 213 // ClientIdGen ... 214 func ClientIdGen(args ...interface{}) string { 215 var b strings.Builder 216 b.WriteString("smarthome") 217 for _, n := range args { 218 fmt.Fprintf(&b, "_%v", n) 219 } 220 return b.String() 221 }