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  }