Giao thức MQTT phù hợp nhất cho các dự án IoT thương mại, nó dáp ứng tốc độ tốt, băng thông ít, độ tin cậy cao. Tài liệu về giao thức MQTT thì các bạn tham khảo ở các trên mạng hoặc 1 số bài sau :
- https://smartfactoryvn.com/technology/internet-of-things/giao-thuc-mqtt-la-gi-nhung-ung-dung-cua-mqtt-nhu-the-nao/
- https://esp8266.vn/nonos-sdk/mqtt/what-is-mqtt/
Mình sẽ không nhắc lại phần lí thuyết nữa vì trên mạng có rất nhiều rồi. Chúng ta sẽ đi vào thực hành làm thử 1 project với giao thức MQTT luôn
Đầu tiên, phía esp8266 các bạn tải thư viện Pubsubclient
Giao thức MQTT cần có 1 server ( gọi là broker) để làm trung tâm của mọi luồng dữ liệu, trong các bài viết sau mình sẽ hướng dẫn các bạn tự build server, còn trong bài này mình sẽ sử dụng server miễn phí không bảo mật là broker.hivemq.com để demo
Các bạn copy chương trình cho esp8266
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
#include <ESP8266WiFi.h> #include <PubSubClient.h> // Thông tin về wifi #define ssid "dieukhien" #define password "12345678" #define mqtt_server "broker.hivemq.com" const uint16_t mqtt_port = 1883; //Port của CloudMQTT TCP WiFiClient espClient; PubSubClient client(espClient); void setup() { Serial.begin(115200); setup_wifi(); client.setServer(mqtt_server, mqtt_port); client.setCallback(callback); } // Hàm kết nối wifi void setup_wifi() { delay(10); Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); } // Hàm call back để nhận dữ liệu void callback(char* topic, byte* payload, unsigned int length) { Serial.print("Co tin nhan moi tu topic:"); Serial.println(topic); for (int i = 0; i < length; i++) Serial.print((char)payload[i]); Serial.println(); } // Hàm reconnect thực hiện kết nối lại khi mất kết nối với MQTT Broker void reconnect() { while (!client.connected()) // Chờ tới khi kết nối { // Thực hiện kết nối với mqtt user và pass if (client.connect("ESP8266_id1","ESP_offline",0,0,"ESP8266_id1_offline")) //kết nối vào broker { Serial.println("Đã kết nối:"); client.subscribe("IoT47_MQTT_Test"); //đăng kí nhận dữ liệu từ topic IoT47_MQTT_Test } else { Serial.print("Lỗi:, rc="); Serial.print(client.state()); Serial.println(" try again in 5 seconds"); // Đợi 5s delay(5000); } } } unsigned long t; void loop() { if (!client.connected())// Kiểm tra kết nối reconnect(); client.loop(); if(millis() - t > 500) //nếu 500 mili giây trôi qua { t=millis(); Serial.print("Gui tin nhan \"Xin chao\" vao topic IoT47_MQTT_Test"); client.publish("IoT47_MQTT_Test", "Xin chao !"); // gửi dữ liệu lên topic IoT47_MQTT_Test } } |
- Ở dòng 5 và 6 các bạn đổi thành wifi của mình.
- Hàm callback là hàm gọi lại khi có dữ liệu gửi đến topic mà chúng ta đăng kí
- Hàm client.subscribe dùng để đăng kí 1 topic
- Hàm client.publish dùng để gửi dữ liệu lên 1 topic
- Hàm client.connect để kết nối vào MQTT Broker, với các tham số
ESP8266_id1 : Id của thiết bị đăng kí vào (có thể chỉnh sửa bất thành bất kì)
ESP_offline : khi thiết bị (esp8266) mất mạng (offline) thì broker sẽ xuất bản
1 tin nhắn vào topic này
ESP8266_offline : Nội dung của tin nhắn offline
Sau khi kết nối thành công ở dòng 53 mình đăng kí topic IoT47_MQTT_Test và trong hàm loop xuất bản tin nhắn “Xin chao” vào chính topic IoT47_MQTT_Test .Như vậy chúng ta sẽ nhận lại được chính tin nhắn mà ta đã xuất bản !
Kết quả: Mình đã nhận được chính tin nhắn mà mình xuất bản lên
Thiết kế giao diện web gửi tin nhắn cho ESP8266
Để có thể kết nối tới MQTT broker, mình sẽ sử dụng ngôn ngữ JavaScript và thư viện PahoMQTT
Các bạn tạo 1 file tên là index.html và thêm mã code web
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
<!DOCTYPE html> <html> <head> <title>Demo MQTT</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta charset="utf-8"> <script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.js" type="text/javascript"></script> <script type = "text/javascript" language = "javascript"> var max,at_OK; function makeid() { var text = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for (var i = 0; i < 5; i++) text += possible.charAt(Math.floor(Math.random() * possible.length)); return text; } // Create a client instance var client = new Paho.MQTT.Client("broker.hivemq.com", 8000, makeid()); // set callback handlers client.onConnectionLost = onConnectionLost; client.onMessageArrived = onMessageArrived; var options = { useSSL: false, userName: "", password: "", onSuccess:onConnect, onFailure:doFail } console.log("Connect to broker.hivemq.com:8000"); // connect the client client.connect(options); function doFail(e){ console.log(e); } function onConnect() //sự kiên kết nối thành công { console.log("Connect OK"); client.subscribe("IoT47_MQTT_Test"); //đăng kí kênh } // called when the client loses its connection function onConnectionLost(responseObject) { if (responseObject.errorCode !== 0) { console.log(responseObject.errorMessage); } } // called when a message arrives function onMessageArrived(message) { console.log(message.destinationName + ":" +message.payloadString); } function public (topic,data) { message = new Paho.MQTT.Message(data); message.destinationName = topic; client.send(message); } </script> </head> <body> </body> </html> |
- Hàm makeID có nhiệm vụ tạo ra 1 id ngẫu nhiên để web kết nối tới MQTT broker tránh bị trùng id
- Mình sẽ kết nối vào broker.hivemq.com qua cổng 8000 vì cổng 8000 là cổng dành cho các kết nối thông qua Socket, trong khi cổng 1883 dành cho các kết nối qua TCP
- Hàm public dùng để xuất bản 1 tín nhắn tới 1 topic nào dó
- Hàm client.subscribe dùng để đăng kí nhận tin nhắn từ 1 topic nào đó
- Hàm onMessageArrived là hàm callback khi có 1 tin nhắn từ 1 topic đã đăng kí gửi tới
Mình sẻ lưu lại và mở file này bằng trình duyệt, sau đó ấn F12 để xem dữ liệu debug in ra màn hình console
Các bạn có thể thấy sau khi thông báo Connect OK thì chúng ta đã nhận được tin nhắn “Xin chao !” mà esp8266 liên tục gửi lên mỗi 500 mili giây
Cũng trong màn hình debug, mình gõ hàm public để gửi thử dữ liệu đến esp8266 nhé !
Thiết kế giao diện nút nhấn điều khiển thiết bị cho giao thức mqtt
Chúng ta sẽ tận dùng lại giao diện dã xài ở bài 5 nhé
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
<!DOCTYPE html> <html> <head> <title>Demo MQTT</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta charset="utf-8"> <script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.js" type="text/javascript"></script> <script type = "text/javascript" language = "javascript"> var max,at_OK; function makeid() { var text = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for (var i = 0; i < 5; i++) text += possible.charAt(Math.floor(Math.random() * possible.length)); return text; } // Create a client instance var client = new Paho.MQTT.Client("broker.hivemq.com", 8000, makeid()); // set callback handlers client.onConnectionLost = onConnectionLost; client.onMessageArrived = onMessageArrived; var options = { useSSL: false, userName: "", password: "", onSuccess:onConnect, onFailure:doFail } console.log("Connect to broker.hivemq.com:8000"); // connect the client client.connect(options); function doFail(e){ console.log(e); } function onConnect() //sự kiên kết nối thành công { console.log("Connect OK"); client.subscribe("ESP8266_sent_data"); //đăng kí kênh } // called when the client loses its connection function onConnectionLost(responseObject) { if (responseObject.errorCode !== 0) { console.log(responseObject.errorMessage); } } // called when a message arrives function onMessageArrived(message) { console.log(message.destinationName + ":" +message.payloadString); document.getElementById("tinnhan").innerHTML = "Tin nhắn từ esp8266: " + message.payloadString; } function public (topic,data) { message = new Paho.MQTT.Message(data); message.destinationName = topic; client.send(message); } </script> <style> .b{width: 100px;height: 40px;font-size: 21px;color: #FFF;background-color:#4caf50;border-radius: 10px;} .t{width: 100px;height: 40px;font-size: 21px;color: #FFF;background-color:#f44336;border-radius: 10px;} </style> </head> <body> <div style="width: 330px;height: auto;margin: 0 auto;margin-top: 70px"> <h1 align="center">Điều khiển thiết bị qua WIFI - MQTT</h1> <p align="center" id="tinnhan">Tin nhắn từ esp8266: ... </p> <table align="center"> <tr> <td><button class='b' onclick="public('ESP8266_read_data','Bật 1')">Bật 1</button><td> <td><button class='t' onclick="public('ESP8266_read_data','Tắt 1')">Tắt 1</button><td> <tr> <tr> <td><button class='b' onclick="public('ESP8266_read_data','Bật 2')">Bật 2</button><td> <td><button class='t' onclick="public('ESP8266_read_data','Tắt 2')">Tắt 2</button><td> <tr> <tr> <td><button class='b' onclick="public('ESP8266_read_data','Bật 3')">Bật 3</button><td> <td><button class='t' onclick="public('ESP8266_read_data','Tắt 3')">Tắt 3</button><td> <tr> <tr> <td><button class='b' onclick="public('ESP8266_read_data','Bật 4')">Bật 4</button><td> <td><button class='t' onclick="public('ESP8266_read_data','Tắt 4')">Tắt 4</button><td> <tr> </table> </div> </body> </html> |
Điều khiển thiết bị qua WIFI - MQTT
Tin nhắn từ esp8266: ...
Khi ấn nút ( sự kiện OnClick) mình sẽ cho xuất bản tin nhắn tương ứng tới ESP8266 qua topic ESP8266_read_data. Do vậy ở code esp8266 mình sẽ sửa lại code cho nó đăng kí vào topic ESP8266_read_data và cho web đăng kí vào kênh ESP8266_sent_data để nhận tin nhắn esp8266 gửi lên
Mình cũng thêm đoạn mã đọc data tử cổng Serial để gửi lên cho web
Full code cho esp8266
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
#include <ESP8266WiFi.h> #include <PubSubClient.h> // Thông tin về wifi #define ssid "dieukhien" #define password "12345678" #define mqtt_server "broker.hivemq.com" const uint16_t mqtt_port = 1883; //Port của CloudMQTT TCP WiFiClient espClient; PubSubClient client(espClient); void setup() { Serial.begin(115200); setup_wifi(); client.setServer(mqtt_server, mqtt_port); client.setCallback(callback); } // Hàm kết nối wifi void setup_wifi() { delay(10); Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); } // Hàm call back để nhận dữ liệu void callback(char* topic, byte* payload, unsigned int length) { Serial.print("Co tin nhan moi tu topic:"); Serial.println(topic); for (int i = 0; i < length; i++) Serial.print((char)payload[i]); Serial.println(); } // Hàm reconnect thực hiện kết nối lại khi mất kết nối với MQTT Broker void reconnect() { while (!client.connected()) // Chờ tới khi kết nối { // Thực hiện kết nối với mqtt user và pass if (client.connect("ESP8266_id1","ESP_offline",0,0,"ESP8266_id1_offline")) //kết nối vào broker { Serial.println("Đã kết nối:"); client.subscribe("ESP8266_read_data"); //đăng kí nhận dữ liệu từ topic ESP8266_read_data } else { Serial.print("Lỗi:, rc="); Serial.print(client.state()); Serial.println(" try again in 5 seconds"); // Đợi 5s delay(5000); } } } void loop() { if (!client.connected())// Kiểm tra kết nối reconnect(); client.loop(); if(Serial.available() > 0) { delay(30); char inputString[30]=""; int i=0; while(Serial.available() > 0) { char inChar = (char)Serial.read(); inputString[i++] = inChar; } client.publish("ESP8266_sent_data", inputString); // gửi dữ liệu lên topic ESP8266_sent_data } } |
hay