github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/integration/mqttclient/mqttclient.go (about)

     1  package mqttclient
     2  
     3  import (
     4  	"crypto/tls"
     5  	"crypto/x509"
     6  	"database/sql"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"os/signal"
    12  	"strconv"
    13  	"syscall"
    14  	"time"
    15  
    16  	mqtt "github.com/eclipse/paho.mqtt.golang"
    17  	"github.com/google/uuid"
    18  	"github.com/mdaxf/iac-signalr/signalr"
    19  	"github.com/mdaxf/iac/com"
    20  	dbconn "github.com/mdaxf/iac/databases"
    21  	"github.com/mdaxf/iac/documents"
    22  	"github.com/mdaxf/iac/framework/queue"
    23  	"github.com/mdaxf/iac/logger"
    24  )
    25  
    26  type MqttConfig struct {
    27  	Mqtts  []Mqtt `json:"mqtts"`
    28  	ApiKey string `json:"apikey"`
    29  }
    30  
    31  type Mqtt struct {
    32  	Type          string      `json:"type"` // tcp, ws, wss
    33  	Broker        string      `json:"broker"`
    34  	Port          string      `json:"port"`
    35  	CertFile      string      `json:"certFile"`
    36  	KeyFile       string      `json:"keyFile"`
    37  	CaCertFile    string      `json:"caFile"`
    38  	Username      string      `json:"username"`
    39  	Password      string      `json:"password"`
    40  	Topics        []MqttTopic `json:"topics"`
    41  	AutoReconnect bool        `json:"reconnect"`
    42  }
    43  
    44  type MqttTopic struct {
    45  	Topic   string `json:"topic"`
    46  	Qos     byte   `json:"qos"`
    47  	Handler string `json:"handler"`
    48  	Mode    string `json:"mode"`
    49  	Type    string `json:"type"`
    50  }
    51  
    52  type MqttClient struct {
    53  	Config         Mqtt
    54  	mqttBrokertype string
    55  	mqttBroker     string
    56  	mqttPort       string
    57  	certFile       string
    58  	keyFile        string
    59  	caCertFile     string
    60  	username       string
    61  	password       string
    62  	mqttClientID   string
    63  	mqttTopics     []MqttTopic
    64  	iLog           logger.Log
    65  	Client         mqtt.Client
    66  	Queue          *queue.MessageQueue
    67  	DocDBconn      *documents.DocDB
    68  	DB             *sql.DB
    69  	SignalRClient  signalr.Client
    70  	monitoring     bool
    71  	AppServer      string
    72  	ApiKey         string
    73  }
    74  
    75  // NewMqttClient creates a new instance of MqttClient with the given configurations.
    76  // It initializes the MqttClient with the provided configurations and returns a pointer to the created MqttClient.
    77  // The MqttClient is initialized with the following parameters:
    78  // - mqttBrokertype: a string representing the type of the MQTT broker (tcp, ws, wss).
    79  // - mqttBroker: a string representing the MQTT broker address.
    80  // - mqttPort: a string representing the MQTT broker port.
    81  // - certFile: a string representing the certificate file path.
    82  // - keyFile: a string representing the key file path.
    83  // - caCertFile: a string representing the CA certificate file path.
    84  // - mqttClientID: a string representing the MQTT client ID.
    85  // - mqttTopics: a slice of MqttTopic structs representing the MQTT topics to subscribe to.
    86  // - iLog: a logger.Log struct representing the logger.
    87  // - Client: a mqtt.Client struct representing the MQTT client.
    88  func NewMqttClient(configurations Mqtt) *MqttClient {
    89  	iLog := logger.Log{ModuleName: logger.Framework, User: "System", ControllerName: "MqttClient"}
    90  
    91  	iLog.Debug(fmt.Sprintf(("Create MqttClient with configuration : %s"), logger.ConvertJson(configurations)))
    92  
    93  	mqttclient := &MqttClient{
    94  		Config:         configurations,
    95  		mqttBrokertype: configurations.Type, // tcp, ws, wss
    96  		mqttBroker:     configurations.Broker,
    97  		mqttPort:       configurations.Port,
    98  		certFile:       configurations.CertFile,
    99  		keyFile:        configurations.KeyFile,
   100  		caCertFile:     configurations.CaCertFile,
   101  		mqttClientID:   (uuid.New()).String(),
   102  		mqttTopics:     configurations.Topics,
   103  		iLog:           iLog,
   104  		monitoring:     false,
   105  		ApiKey:         com.ApiKey,
   106  	}
   107  	iLog.Debug(fmt.Sprintf(("Create MqttClient: %s"), logger.ConvertJson(mqttclient)))
   108  	uuid := uuid.New().String()
   109  
   110  	mqttclient.DocDBconn = documents.DocDBCon
   111  	mqttclient.DB = dbconn.DB
   112  	mqttclient.SignalRClient = com.IACMessageBusClient
   113  	mqttclient.Queue = queue.NewMessageQueue(uuid, "mqttclient")
   114  	mqttclient.Queue.DocDBconn = documents.DocDBCon
   115  	mqttclient.Queue.DB = dbconn.DB
   116  	mqttclient.Queue.SignalRClient = com.IACMessageBusClient
   117  
   118  	//	mqttclient.Initialize_mqttClient()
   119  	return mqttclient
   120  }
   121  
   122  // NewMqttClientbyExternal creates a new instance of MqttClient with the provided configurations, database connection, document database connection, and SignalR client.
   123  // It initializes the MqttClient with the given configurations and returns the created MqttClient instance.
   124  // The MqttClient is initialized with the following parameters:
   125  // - mqttBrokertype: a string representing the type of the MQTT broker (tcp, ws, wss).
   126  // - mqttBroker: a string representing the MQTT broker address.
   127  // - mqttPort: a string representing the MQTT broker port.
   128  // - certFile: a string representing the certificate file path.
   129  // - keyFile: a string representing the key file path.
   130  // - caCertFile: a string representing the CA certificate file path.
   131  // - mqttClientID: a string representing the MQTT client ID.
   132  // - mqttTopics: a slice of MqttTopic structs representing the MQTT topics to subscribe to.
   133  // - iLog: a logger.Log struct representing the logger.
   134  func NewMqttClientbyExternal(configurations Mqtt, DB *sql.DB, DocDBconn *documents.DocDB, SignalRClient signalr.Client) *MqttClient {
   135  	iLog := logger.Log{ModuleName: logger.Framework, User: "System", ControllerName: "MqttClient"}
   136  
   137  	iLog.Debug(fmt.Sprintf(("Create MqttClient with configuration : %s"), logger.ConvertJson(configurations)))
   138  
   139  	mqttclient := &MqttClient{
   140  		mqttBrokertype: configurations.Type, // tcp, ws, wss
   141  		mqttBroker:     configurations.Broker,
   142  		mqttPort:       configurations.Port,
   143  		certFile:       configurations.CertFile,
   144  		keyFile:        configurations.KeyFile,
   145  		caCertFile:     configurations.CaCertFile,
   146  		mqttClientID:   (uuid.New()).String(),
   147  		mqttTopics:     configurations.Topics,
   148  		iLog:           iLog,
   149  		DocDBconn:      DocDBconn,
   150  		DB:             DB,
   151  		SignalRClient:  SignalRClient,
   152  		monitoring:     false,
   153  	}
   154  	iLog.Debug(fmt.Sprintf(("Create MqttClient: %s"), logger.ConvertJson(mqttclient)))
   155  	uuid := uuid.New().String()
   156  
   157  	mqttclient.Queue = queue.NewMessageQueuebyExternal(uuid, "mqttclient", DB, DocDBconn, SignalRClient)
   158  
   159  	//	mqttclient.Initialize_mqttClient()
   160  	return mqttclient
   161  }
   162  
   163  // Initialize_mqttClient initializes the MQTT client by setting up the client options,
   164  // connecting to the MQTT broker, subscribing to the desired MQTT topics,
   165  // and starting a goroutine to handle incoming MQTT messages.
   166  // It takes no parameters and returns nothing.
   167  
   168  func (mqttClient *MqttClient) Initialize_mqttClient() {
   169  	// Create an MQTT client options
   170  	opts := mqtt.NewClientOptions()
   171  	opts.SetCleanSession(true)
   172  	opts.SetAutoReconnect(true)
   173  	opts.SetClientID(mqttClient.mqttClientID)
   174  	opts.SetUsername(mqttClient.username)
   175  	opts.SetPassword(mqttClient.password)
   176  	mqttClient.iLog.Debug(fmt.Sprintf("%s://%s:%s", mqttClient.mqttBrokertype, mqttClient.mqttBroker, mqttClient.mqttPort))
   177  
   178  	if mqttClient.mqttBrokertype == "" {
   179  		opts.AddBroker(fmt.Sprintf("%s:%s", mqttClient.mqttBroker, mqttClient.mqttPort)) // Replace with your MQTT broker address
   180  	} else {
   181  		opts.AddBroker(fmt.Sprintf("%s://%s:%s", mqttClient.mqttBrokertype, mqttClient.mqttBroker, mqttClient.mqttPort)) // Replace with your MQTT broker address
   182  	}
   183  
   184  	if mqttClient.mqttBrokertype == "ssl" {
   185  		// Load client cert
   186  		cert, err := tls.LoadX509KeyPair(mqttClient.certFile, mqttClient.keyFile)
   187  		if err != nil {
   188  			mqttClient.iLog.Critical(fmt.Sprintf("Failed to load client certificates: %v", err))
   189  
   190  		}
   191  		opts.SetTLSConfig(&tls.Config{
   192  			Certificates: []tls.Certificate{cert},
   193  			RootCAs:      mqttClient.loadCACert(mqttClient.caCertFile),
   194  		})
   195  	}
   196  	// Create an MQTT client
   197  	client := mqtt.NewClient(opts)
   198  	mqttClient.Client = client
   199  
   200  	// Set the message handler
   201  	//client.SetDefaultPublishHandler(messageHandler)
   202  	// Connect to the MQTT broker
   203  
   204  	err := mqttClient.Connect()
   205  
   206  	if err != nil {
   207  		return
   208  	}
   209  
   210  	if !mqttClient.monitoring {
   211  		go func() {
   212  			mqttClient.MonitorAndReconnect()
   213  		}()
   214  	}
   215  
   216  }
   217  
   218  func (mqttClient *MqttClient) Connect() error {
   219  	if token := mqttClient.Client.Connect(); token.Wait() && token.Error() != nil {
   220  		mqttClient.iLog.Critical(fmt.Sprintf("Failed to connect to MQTT broker: %v", token.Error()))
   221  		return token.Error()
   222  	}
   223  
   224  	go func() {
   225  		mqttClient.SubscribeTopics()
   226  	}()
   227  
   228  	return nil
   229  }
   230  
   231  func (mqttClient *MqttClient) SubscribeTopics() {
   232  	// Create a channel to receive MQTT messages
   233  	messageChannel := make(chan mqtt.Message)
   234  
   235  	// Define the MQTT message handler
   236  	messageHandler := func(client mqtt.Client, msg mqtt.Message) {
   237  		messageChannel <- msg
   238  	}
   239  
   240  	// Subscribe to the desired MQTT topic(s)
   241  	for _, data := range mqttClient.mqttTopics {
   242  		//topic := data["topic"].(string)
   243  
   244  		topic := data.Topic
   245  		qos := data.Qos
   246  
   247  		if token := mqttClient.Client.Subscribe(topic, qos, messageHandler); token.Wait() && token.Error() != nil {
   248  			mqttClient.iLog.Error(fmt.Sprintf("Failed to subscribe to MQTT topic: %v", token.Error()))
   249  		}
   250  		mqttClient.iLog.Debug(fmt.Sprintf("Subscribed to topic: %s", topic))
   251  	}
   252  
   253  	// Start a goroutine to handle MQTT messages
   254  	go func() {
   255  		for {
   256  			select {
   257  			case msg := <-messageChannel:
   258  				// Process the received MQTT message
   259  				mqttClient.iLog.Debug(fmt.Sprintf("Received message: %s from topic: %s , %d ,%d", msg.Payload(), msg.Topic(), msg.MessageID(), msg.Qos()))
   260  				handler := ""
   261  				mode := ""
   262  				executetype := ""
   263  				for _, data := range mqttClient.mqttTopics {
   264  					if data.Topic == msg.Topic() {
   265  						handler = data.Handler
   266  						mode = data.Mode
   267  						executetype = data.Type
   268  
   269  						mqttClient.iLog.Debug(fmt.Sprintf("topic: %s handler: %s mode: %s type: %s", data.Topic, handler, mode, executetype))
   270  
   271  						break
   272  					}
   273  				}
   274  				if executetype == "local" {
   275  					message := queue.Message{
   276  						Id:        strconv.FormatUint(uint64(msg.MessageID()), 10),
   277  						UUID:      uuid.New().String(),
   278  						Retry:     3,
   279  						Execute:   0,
   280  						Topic:     msg.Topic(),
   281  						PayLoad:   msg.Payload(),
   282  						Handler:   handler,
   283  						CreatedOn: time.Now().UTC(),
   284  					}
   285  					mqttClient.iLog.Debug(fmt.Sprintf("Push message %v to queue: %s", message, mqttClient.Queue.QueueID))
   286  					mqttClient.Queue.Push(message)
   287  				} else {
   288  
   289  					mqttClient.CallWebService(msg, msg.Topic(), handler)
   290  
   291  				}
   292  
   293  			}
   294  		}
   295  	}()
   296  
   297  	// Wait for termination signal to gracefully shutdown
   298  	mqttClient.waitForTerminationSignal()
   299  }
   300  
   301  func (mqttClient *MqttClient) CallWebService(msg mqtt.Message, topic string, handler string) {
   302  
   303  	method := "POST"
   304  	url := mqttClient.AppServer + "/trancode/execute"
   305  
   306  	var result map[string]interface{}
   307  	err := json.Unmarshal(msg.Payload(), &result)
   308  	if err != nil {
   309  		mqttClient.iLog.Error(fmt.Sprintf("Error:", err))
   310  		return
   311  	}
   312  
   313  	var inputs map[string]interface{}
   314  
   315  	inputs["Payload"] = result
   316  	inputs["Topic"] = topic
   317  
   318  	data := make(map[string]interface{})
   319  	data["TranCode"] = handler
   320  	data["Inputs"] = inputs
   321  
   322  	headers := make(map[string]string)
   323  	headers["Content-Type"] = "application/json"
   324  	headers["Authorization"] = "apikey " + mqttClient.ApiKey
   325  
   326  	result, err = com.CallWebService(url, method, data, headers)
   327  
   328  	if err != nil {
   329  		mqttClient.iLog.Error(fmt.Sprintf("Error in WebServiceCallFunc.Execute: %s", err))
   330  		return
   331  	}
   332  
   333  	/*
   334  
   335  		client := &http.Client{}
   336  
   337  		type MSGData struct {
   338  			TranCode string                 `json:"code"`
   339  			Inputs   map[string]interface{} `json:"inputs"`
   340  		}
   341  
   342  		var result map[string]interface{}
   343  		err := json.Unmarshal(msg.Payload(), &result)
   344  		if err != nil {
   345  			mqttClient.iLog.Error(fmt.Sprintf("Error:", err))
   346  			return
   347  		}
   348  		var inputs map[string]interface{}
   349  
   350  		inputs["Payload"] = result
   351  		inputs["Topic"] = topic
   352  
   353  		msgdata := &MSGData{
   354  			TranCode: handler,
   355  			Inputs:   inputs,
   356  		}
   357  
   358  		bytesdata, err := json.Marshal(msgdata)
   359  		if err != nil {
   360  			mqttClient.iLog.Error(fmt.Sprintf("Error:", err))
   361  			return
   362  		}
   363  
   364  		req, err := http.NewRequest(method, url, bytes.NewBuffer(bytesdata))
   365  
   366  		if err != nil {
   367  			mqttClient.iLog.Error(fmt.Sprintf("Error in WebServiceCallFunc.Execute: %s", err))
   368  			return
   369  		}
   370  		req.Header.Set("Content-Type", "application/json")
   371  		req.Header.Set("Authorization", "apikey "+mqttClient.ApiKey)
   372  
   373  		resp, err := client.Do(req)
   374  		if err != nil {
   375  			mqttClient.iLog.Error(fmt.Sprintf("Error in WebServiceCallFunc.Execute: %s", err))
   376  			return
   377  		}
   378  		defer resp.Body.Close()
   379  
   380  		respBody, err := ioutil.ReadAll(resp.Body)
   381  		err = json.Unmarshal(respBody, &result)
   382  		if err != nil {
   383  			mqttClient.iLog.Error(fmt.Sprintf("Error:", err))
   384  			return
   385  		}
   386  	*/
   387  	mqttClient.iLog.Debug(fmt.Sprintf("Response data: %v", result))
   388  
   389  }
   390  
   391  func (mqttClient *MqttClient) MonitorAndReconnect() {
   392  	startTime := time.Now()
   393  	defer func() {
   394  		elapsed := time.Since(startTime)
   395  		mqttClient.iLog.PerformanceWithDuration("MQTTClient.monitorAndReconnect", elapsed)
   396  	}()
   397  
   398  	// Recover from any panics and log the error
   399  	defer func() {
   400  		if err := recover(); err != nil {
   401  			mqttClient.iLog.Error(fmt.Sprintf("MQTTClient.monitorAndReconnect defer error: %s", err))
   402  			//	ctx.JSON(http.StatusBadRequest, gin.H{"error": err})
   403  		}
   404  	}()
   405  	mqttClient.iLog.Debug(fmt.Sprintf("Start the mqttclient connection monitoring for %s", mqttClient.mqttBroker))
   406  	mqttClient.monitoring = true
   407  
   408  	for {
   409  		isconnected := mqttClient.Client.IsConnected()
   410  		if !isconnected {
   411  			mqttClient.iLog.Error(fmt.Sprintf("Mqttclient connection lost, %v %v", mqttClient.mqttBroker, mqttClient.mqttPort))
   412  
   413  			err := mqttClient.Connect()
   414  
   415  			if err != nil {
   416  				mqttClient.iLog.Error("Reconnect to mqtt broker fail!")
   417  				time.Sleep(5 * time.Second)
   418  			} else {
   419  				time.Sleep(10 * time.Second)
   420  			}
   421  
   422  		} else {
   423  			time.Sleep(10 * time.Second)
   424  		}
   425  	}
   426  
   427  }
   428  
   429  // Publish publishes a message to the MQTT broker with the specified topic and payload.
   430  // It waits for the operation to complete and logs the result.
   431  // It takes two parameters:
   432  // - topic: a string representing the MQTT topic to publish to.
   433  // - payload: a string representing the MQTT message payload.
   434  // It returns nothing.
   435  
   436  func (mqttClient *MqttClient) Publish(topic string, payload string) {
   437  
   438  	token := mqttClient.Client.Publish(topic, 0, false, payload)
   439  	token.Wait()
   440  	if token.Error() != nil {
   441  		mqttClient.iLog.Debug(fmt.Sprintf("Failed to publish message: topic %s, payload %s\n %s\n with error:", topic, payload, token.Error()))
   442  	} else {
   443  		mqttClient.iLog.Debug(fmt.Sprintf("Message published successfully: topic %s, payload %s\n", topic, payload))
   444  	}
   445  }
   446  
   447  // loadCACert loads the CA certificate from the specified file and returns a *x509.CertPool.
   448  // It reads the contents of the file using ioutil.ReadFile and appends the certificate to a new CertPool.
   449  // If there is an error reading the file, it logs an error message and returns nil.
   450  // It takes one parameter:
   451  // - caCertFile: a string representing the CA certificate file path.
   452  // It returns a *x509.CertPool.
   453  
   454  func (mqttClient *MqttClient) loadCACert(caCertFile string) *x509.CertPool {
   455  	caCert, err := ioutil.ReadFile(caCertFile)
   456  	if err != nil {
   457  		mqttClient.iLog.Error(fmt.Sprintf("Failed to read CA certificate: %v", err))
   458  	}
   459  	caCertPool := x509.NewCertPool()
   460  	caCertPool.AppendCertsFromPEM(caCert)
   461  	return caCertPool
   462  }
   463  
   464  // waitForTerminationSignal waits for an interrupt signal or a termination signal and performs a graceful shutdown of the MQTT client.
   465  // It listens for the os.Interrupt and syscall.SIGTERM signals and upon receiving the signal, it disconnects the MQTT client, performs any necessary cleanup or graceful shutdown logic, and exits the program.
   466  func (mqttClient *MqttClient) waitForTerminationSignal() {
   467  	c := make(chan os.Signal, 1)
   468  	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
   469  	<-c
   470  	fmt.Println("\nShutting down...")
   471  	mqttClient.Client.Disconnect(250)
   472  	time.Sleep(2 * time.Second) // Add any cleanup or graceful shutdown logic here
   473  	os.Exit(0)
   474  }