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 }