Nếu bạn chưa biết cách kết nối module ESP8266 với Arduino, bạn có thể đọc bài viết:

Cài đặt thư viện của ESP8266 ESP-12E NodeMCU để lập trình trong Arduino

Mặc dù có nhiều chương trình có sẵn dành cho ESP8266 kết nối với MQTT, tôi vẫn nhận được câu hỏi của mọi người về cách thực hiện nó. Vì vậy, tôi sẽ giải thích từng bước ở đây. Nếu bạn đã biết chính xác về cách làm điều này, bạn cũng có thể bỏ qua bài đăng này, mặc dù có thể có một số điều thú vị hơn về phía cuối.

Đối với những người thực sự không biết MQTT là gì, thì nó là một giao thức gọn nhẹ để giao tiếp với máy tính. Nó bao gồm một kênh (topic) và thông điệp (message  - còn gọi là payload) được sử dụng thường xuyên trong các dự án IOT và nhà thông minh.

Ví dụ, hơi vô lý một chút, tủ lạnh có thể nói chuyện với lò nướng và máy pha cà phê của bạn nhờ MQTT, miễn tất cả phải là 'thiết bị thông minh. Tuy nhiên, chúng không nói chuyện trực tiếp với nhau, chúng làm điều đó thông qua một máy tính trung gian gọi là broker. Broker theo dõi tất cả lưu lượng truy cập MQTT và biết thông điệp nào cần phải chuyển đến thiết bị nào. Điều đó có nghĩa là khi tủ lạnh có thể muốn nói chuyện với máy pha cà phê, nhưng lại không muốn lò nướng để biết nội dung nói chuyện đó.

Vì vậy, trước hết bạn cần một MQTT broker. Đó có thể là một broker công cộng dùng chung hoặc broker của riêng bạn (ví dụ trên một Raspberry Pi). Mosquitto là một broker phổ biến tự vận hành.

Nếu bạn không muốn chạy broker của riêng mình, bạn có các tùy chọn như test.mosquitto.org, CloudMQTT, HiveMQMQTT Dashboard, RabbitMQio.adafruit. Để bắt đầu và ứng dụng nhanh, test.mosquitto.org là dễ nhất. Tuy nhiên, nó là một broker thử nghiệm hoàn toàn mở. Không có bảo mật gì với nó vì vậy nó được khuyến khích để thay đổi bằng một broker riêng hoặc có mật khẩu bảo vệ ngay sau khi bạn có MQTT của bạn và vận hành nó.

Trong chương trình ESP8266 dưới đây, sẽ có 4 thông tin quan trọng. Đó là máy chủ, người dùng, mật khẩu và cổng (port). Hãy nhớ rằng “test.mosquitto.org” không yêu cầu người dùng đăng nhập và khi bạn đang vận hành một broker của riêng bạn, bạn không cần đăng nhập (nhưng có thể đặt), nhưng tôi sẽ giới thiệu hai biến này trong chương trình để dành khi cần sử dụng.

ESP8266 sử dụng giao thức MQTT được coi là "client - khách" của "broker". Để cho ESP8266 nói chuyện với broker PubSubClient là 1 thư viện rất phổ biến. Thư viện đó được gọi là 'PubSub' vì nó cho phép ESP 'publish - xuất bản' các tin nhắn MQTT cho broker và để 'Subscribe - Đăng ký nhận tin' với các tin nhắn MQTT của broker. Bạn có nhớ tôi đã nói lò nướng không cần phải nghe tủ lạnh nói chuyện với máy pha cà phê? Vâng, đó là những gì 'đăng ký' là dành cho ESP8266 cụ thể chỉ nhận được thông báo đã đăng ký. Thư viện có thể được cài đặt thông qua trình quản lý thư viện IDE Arduino.

Mã nguồn chương trình

Giống như tất cả các chương trình, chúng ta sẽ gọi các thư việ cần thiết cho các chức năng cần thiết. Chúng ta cần thư viện ESP8266WiFi để ESP8266 kết nối mạng WiFi, và thư viện PubSubClient để ESP8266 kết nối với MQTT broker và tin nhắn publish/subscribe trên các kênh.

#include <PubSubClient.h>

#include <ESP8266WiFi.h>

#include <WiFiClient.h

Tiếp theo, chúng ta sẽ khai báo các biến toàn cục cho thông tin đăng nhập WiFi.

constchar* ssid = "YourWiFiSSID";  

constchar* password = "YourWiFiPassword";

Tiếp theo chúng ta cần thông tin của máy chủ MQTT. Như đã giải thích ở trên, bạn sẽ cần địa chỉ máy chủ, cổng, tên người dùng và mật khẩu, trong chương trình này, chúng ta có thể để trống người dùng và mật khẩu. Hầu hết các broker MQTT đều sử dụng cổng 1883.

const char* mqttServer = "test.mosquitto.com";

const int mqttPort = 1883;

const char* mqttUser = "YourMQTTUsername";

const char* mqttPassword = "YourMQTTPassword";

Tiếp theo chúng ta sẽ khai báo hai "đối tượng" (giống như với tất cả các thư viện): đối tượng WiFiClient thiết lập kết nối đến một IP và cổng cụ thể. PubSubClient cung cấp dữ liệu đầu vào cho WiFiClient mà chúng ta vừa tạo.

Chúng ta sẽ giữ đối tượng PubSubclient đơn giản chỉ với một đối số, nhưng nó có thể nhận được nhiều đối số hơn. Bạn có thể muốn kiểm tra tài liệu API.

WiFiClient espClient;

PubSubClient client(espClient);

Trong hàm setup, chúng tôi kết nối với mạng WiFi và gửi trạng thái hoạt động đến serial monitor.

Serial.begin(115200);

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print("Connecting to WiFi..");
}

Serial.println("Connected to the WiFi network");

Sau đó, chúng tôi thiết lập địa chỉ và cổng của máy chủ MQTT. Chúng ta làm điều này với phương thức setServer của đối tượng PubSubClient.

client.setServer(mqttServer, mqttPort);

Chúng ta cần phải cho chương trình biết phải làm gì với các thông điệp MQTT nhận được. Để làm điều đó, chúng ta sử dụng phương thức setCallback của đối tượng PubSub để thiết lập một hàm được thực thi khi nhận được một thông điệp MQTT. Thường thì, chức năng này được gọi là ‘callback’, nhưng bạn có thể đặt cho nó tên khác.

client.setCallback(callback);

Bây giờ, chúng ta cần hàm Setup để kết nối với máy chủ MQTT. Tốt hơn là chúng ta làm điều này trong một vòng lặp cho đến khi thành công.

while (!client.connected()) {
  Serial.println("Connecting to MQTT...");
  if (client.connect("ESP8266Client", mqttUser, mqttPassword )) {
    Serial.println("connected");
  } else {
    Serial.print("failed with state ");
    Serial.print(client.state());
    delay(2000);
  }
}

Hàm setup là một nơi để xuất bản thông điệp MQTT đầu tiên của chúng ta với thế giới. Chuỗi "Hello World" để cho broker biết chúng ta đang trực tuyến. Chúng ta làm điều này với phương thức publish, với một kênh và một thông điệp làm đối số. Chúng tôi chọn kênh “test/esp8266” và cho một thông điệp “Hello ESP world”.

client.publish("test/esp8266","Hello ESP world");

Chúng ta hãy đăng ký một kênh để cho broker biết thông điệp nào từ publisher nào mà chúng ta muốn nhận nhất. Chúng ta làm điều đó với phương thức subscribe, với tên kênh làm đối số. Chúng ta có thể lấy bất kỳ kênh nào chúng ta muốn, nhưng bây giờ hãy đăng ký vào cùng một kênh như lúc chúng ta xuất bản.

client.subscribe("test/esp8266");

Hàm setup đầy đủ bây giờ trông giống như sau:

void setup()
{
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.println("Connecting to WiFi..");
  }
  Serial.println("Connected to the WiFi network");
  client.setServer(mqttServer, mqttPort);
  client.setCallback(callback);
  while (!client.connected()) {
    Serial.println("Connecting to MQTT...");
    if (client.connect("ESP8266Client", mqttUser, mqttPassword )) {
      Serial.println("connected");
    } else {
      Serial.print("failed with state ");
      Serial.print(client.state());
      delay(2000);
    }
  }
  client.publish("test/esp8266", "Hello ESP world");
  client.subscribe("test/esp8266");
}

Kênh

Bây giờ chúng ta sẽ nói về định dạng của kênh. Ở dạng nhỏ nhất, kênh chỉ cần dài một ký tự.

Theo thông lệ, các kênh có một hệ thống phân cấp được xác định bởi các dấu gạch chéo ngược. Một chủ đề ví dụ có thể là “home\garden\lawn\sprinkler”.

Hệ thống phân cấp này cho phép đăng ký ở nhiều cấp độ. Ví dụ, bạn có thể đăng ký chủ đề “home\#” để nhận tất cả các tin nhắn xử lý ‘home’. Bạn có thể đăng ký chủ đề “home\garden\#” để nhận tất cả các tin nhắn liên quan đến khu vườn của bạn, v.v. Thường thì bạn sẽ thấy một chủ đề bắt đầu bằng dấu gạch chéo ngược, như “\home\garden\lawn\sprinkler ". Hoàn toàn không có lợi ích khi bắt đầu một chủ đề với dấu gạch chéo ngược.

Hàm callback

Hàm callback nhận các thông điệp MQTT (Kênh + payload) mà trình khách ESP PubSub của chúng ta đã đăng ký *). Các đối số của hàm callback là tên của kênh, payload và độ dài của payload đó.

Như trong ví dụ, chúng ta sẽ thiết lập chức năng in kênh sang cổng nối tiếp và sau đó in payload đã nhận. Vì chúng ta cũng có độ dài của payload làm đối số của hàm, điều này có thể dễ dàng thực hiện trong một vòng lặp.

void callback(char* topic, byte* payload, unsigned int length)
{
  Serial.print("Message arrived in topic: ");
  Serial.println(topic);
  Serial.print("Message:");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
}

*) Để chính xác: đăng ký là một kênh, sau đó client sẽ nhận được kênh + payload đưa đến với kênh đó. Payload đôi khi được gọi là ‘thông điệp’ nhưng gây nhầm lẫn kênh + payload đôi khi còn được gọi là “thông điệp”.

Trong ví dụ trên, chúng ta chỉ đăng ký một kênh cụ thể:

test/esp8266.

Ví dụ, kênh “test/esp8266/node1” sẽ không xuất hiện. Để làm được điều đó, chúng ta cần chỉ định ký tự đại diện “#”.

Hàm loop

Trong hàm loop chính, chúng ta sử dụng phương thức loop của PubSubClient. Hàm này được gọi để cho phép máy khách xử lý các tin nhắn đến và duy trì kết nối nó với máy chủ.

void loop()
{
  client.loop();
}

Mã nguồn đầy đủ:

#include <WiFiClient.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
const char* ssid = "YourWiFiSSID ";
const char* password =  "YourWiFiPassword";
const char* mqttServer = "test.mosquitto.com";
const int mqttPort = 1883;
const char* mqttUser = "YourMQTTUsername";
const char* mqttPassword = "YourMQTTPassword";
WiFiClient espClient;
PubSubClient client(espClient);
void setup()
{
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.println("Connecting to WiFi..");
  }
  Serial.println("Connected to the WiFi network");
  client.setServer(mqttServer, mqttPort);
  client.setCallback(callback);
  while (!client.connected()) {
    Serial.println("Connecting to MQTT...");
    if (client.connect("ESP8266Client", mqttUser, mqttPassword ))
    {
      Serial.println("connected");
    } else {
      Serial.print("failed with state ");
      Serial.print(client.state());
      delay(2000);
    }
  }
  client.publish("test/esp8266", "Hello ESP World");
  client.subscribe("test/esp8266");// here is where you later add a wildcard
}

void callback(char* topic, byte* payload, unsigned int length)
{
  Serial.print("Message arrived in topic: ");
  Serial.println(topic);
  Serial.print("Message:");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
}

void loop()
{
  client.loop();
}

 

Kiểm tra

Hãy tải lên và chạy nó trên ESP8266 của bạn. Mở serial monitor của Arduino IDE để in kết quả.

ESP8266 sẽ gửi tin nhắn “Hello ESP World”, tuy nhiên, nó sẽ không được in trên màn hình nối tiếp, nhưng khi chương trình cũng đăng ký cùng một kênh, nó sẽ nhận được các tin nhắn tương tự, quay lại với broker và sau đó in nó trên màn hình nối tiếp (serial monitor).

Nếu bất kỳ client nào khác gửi tin nhắn đến chủ đề “test/esp8266”, thì sau đó cũng sẽ được in trong serial monitor.

Bây giờ nếu nó không hoạt động và bạn chắc chắn mã chương trình OK, rất tốt khi cài đặt MQTT-Spy trên máy tính của bạn. MQTT-Spy giám sát lưu lượng truy cập MQTT trên mạng của bạn và nó có thể xuất bản và đăng ký với bất kỳ kênh MQTT mong muốn nào.

OK, vậy giờ thì sao? Làm cách nào để gửi một biến?

Sau niềm vui ban đầu của bạn rằng mọi thứ đang hoạt động, ngay lập tức bạn sẽ muốn gửi một cái gì đó hữu ích ví dụ như đọc một cảm biến DHT22 hoặc DS18B20 và đột nhiên bạn cần phải thực hiện bước của chuỗi “Hello World” thành biến nhiệt độ. Dĩ nhiên, bạn sẽ cố gắng và chỉ cần chèn biến vào vị trí của chuỗi "Hello World" và hy vọng là tốt nhất, nhưng điều đó sẽ thất bại, vì thư viện PubSub (và giao thức MQTT) sẽ chỉ chấp nhận chuỗi.

Bạn sẽ thấy nó thậm chí sẽ không chấp nhận một biến chuỗi, vì vậy phải làm gì? Có một số cách để đặt một biến trong payload MQTT:

1- lấy một thư viện khác.

Có một thư viện PubSubClient viết bởi Ian Tester. Nó sẽ chấp nhận các biến nếu được định nghĩa như sau:

client.publish("topic", String (variable));

Tuy nhiên, nó thường không phải là một sự thay thế, bạn sẽ cần phải thực hiện các điều chỉnh nhỏ trong việc thiết lập những lần gọi khác.

  • Sử dụng thêm ngôn ngữ C++

Giả sử bạn muốn xuất bản một số biến số nguyên có tên là ‘nhiệt độ’

sprintf(buff_msg, "%d", temperature);

client.publish("topic", buff_msg);

Bạn sẽ cần phải thiết lập bộ đệm buff_msg trong khai báo biến toàn cục của bạn như thế này:

buff_msg[32]; // mqtt message

payload được sử dụng rất thường xuyên để gửi sẽ là ON hoặc OFF. Mặc dù thường có thể dễ dàng xác định câu lệnh IF cho payload của bạn như sau:

if (status==1){client.publish("topic","ON");}

if (status==0){client.publish("topic","OFF");}

nó cũng có thể làm điều đó với lệnh sprintf:

sprintf(buff_msg, "%d",  status);

client.publish("topic",buff_msg);

TÔI CÓ THỂ GỬI JSON VỚI MQTT?

Chắc chắn bạn có thể. Tôi khá chắc chắn nó hoạt động với các phương thức trên, sau cùng tất cả JSON chỉ là một số chuỗi ký tự. Tuy nhiên, tôi đề xuất một phương thức khác cho JSON.

Giả sử tôi muốn gửi JSON sau:

"{\"garden\":temperature\",\"data\":["+String(temperature_outside)+","+String(temperature_greenhouse)+","+String(temperature_compost)+","+String(temperature_coldframe)+","+String(temperature_soil)+"]}"

làm điều đó như sau:

String payload="{\"garden\":temperature\",\"data\":["+String(temperature_outside)+","+String(temperature_greenhouse)+","+String(temperature_compost)+","+String(temperature_coldframe)+","+String(temperature_soil)+"]}";

payload.toCharArray(data, (payload.length() + 1));

client.publish("topic",data);

TÔI MUỐN GỬI SỐ IP CỦA TÔI LÀ MQTT, CÓ THỂ CÓ THỂ?

Dĩ nhiên. Có một số phương pháp và cuối cùng tất cả các con đường dẫn về Rome. Sử dụng c_str đôi khi được khuyên nhưng tôi thích những điều sau đây.

trong khai báo biến chung của bạn khai báo:

String IP;

Sau đó, sau khi bạn kết nối với trạng thái WiFi:

IP = WiFi.localIP().toString();

Sau đó, hãy làm như sau:

for (i = 0; i < 16; i++) {
  buff_msg[i] = IP[i];
}
buff_msg[i] = '\0';
client.publish("IPnumber", buff_msg);
}

TÔI CÓ THỂ GỬI ĐỊA CHỈ MAC ?

Có thể. Một lần nữa có một số phương pháp. Tôi thích cái này:

Trong các khai báo biến chung của bạn, khai báo:

String MAC;

Sau đó, sau khi bạn kết nối với trạng thái WiFi:

MAC = WiFi.macAddress();  

Không giống như địa chỉ IP, địa chỉ MAC không cần tới hàm 'toString()'.

Sau đó, nơi bạn xử lý việc xuất bản của mình, hãy làm như sau:

for (i = 0; i < 16; i++) {

  buff_msg[i] = IP[i];

}

buff_msg[i] = '\0';

client.publish("MAC", buff_msg);

}

Thực tế, cách dễ nhất là sử dụng biến gửi PubSubClient của Ian Tester mà tôi đã đề cập trước đó.

Điều này cho phép bạn thực hiện:

IP = WiFi.localIP().toString(); 

MAC = WiFi.macAddress(); 

client.publish("home/ip", String(IP)); 

client.publish("home/mac", String(MAC));

TÔI CÓ THỂ GỬI CÁC BIẾN TRONG KÊNH?

Có, bạn có thể. Trên thực tế, điều này không khác với việc gửi các biến trong payload:

Bạn khai báo một bộ đệm:

buff_topic[30]; // mqtt topic

Giả sử bạn muốn gửi số ID hoặc tên của ESP8266 của bạn như là một phần của kênh và một lỗi là payload:

trong biến toàn cục, bạn đã xác định số hoặc tên của ESP của bạn:

#define nodeId 13 // node ID

và sau đó, nơi bạn gửi tin nhắn của mình:

sprintf(buff_topic, "home/nb/node%02d/error", nodeId);

sprintf(buff_msg, "syntax error %d", error);

client.publish(buff_topic, buff_msg);

NHỮNG GÌ VỀ NHẬN TIN NHẮN MQTT ?

Vâng, tôi đã nói về hàm callback xử lý các tin nhắn gửi đến, nhưng tôi thừa nhận rằng chỉ cần đợi một thông điệp “Hello World” nhưng không phải cần thiết.

Để đăng ký nhiều tin nhắn, chúng tôi cần sử dụng ký tự đại diện, vì vậy,

client.subscribe(test/esp8266);

Sửa thành:

client.subscribe(test/esp8266/#”);

Nó sẽ bắt các thông tin như là:

test/esp8266/Light1

test/esp8266/Light2

Cách rõ ràng nhất để sử dụng chúng là kiểm tra các chủ đề đến là Light1 hoặc Light2, sau đó kiểm tra payload đến (là “ON” hoặc “OFF”) và sau đó hành động theo nội dung của payload.

Đặc biệt nếu bạn có các kênh MQTT với độ dài khác nhau, điều này phải kiểm tra khá nhiều về độ dài của kênh.

Ví dụ:

test/esp8266/kitchenlight

test/esp8266/Livingroomlight

test/esp8266/washingmachine

test/esp/8266/firstfloor/airco

Việc kiểm tra thông điệp, xác định payload sẽ rất tốn bộ nhớ.

Bạn có thể giữ cho các thông điệp MQTT có cùng độ dài và chỉ thay đổi 2 ký tự cuối cùng của kênh để chỉ ra nó là gì. Nếu 1 room có 100 kênh khác nhau, bạn chỉ cần kiểm tra 2 ký tự cuối cùng, chứ không phải là một chuỗi dài.

Ví dụ: 

Giả sử tất cả các tin nhắn MQTT của bạn đều có định dạng: "home/nodeID/devID", thực tế như sau:

“home/node13/dev00”

…….

“home/node13/dev99”

Đăng ký của bạn sau đó sẽ là: “home/node13/#”

và bạn sẽ chỉ phải kiểm tra hai chữ số cuối cùng.

Ví dụ hàm callback:

void callBack(char* topic, byte* payload, unsigned int length) { // receive and handle MQTT messages

  int i;

  for (int i = 0; i < length; i++) {

    Serial.print((char)payload[i]);

  }

  if (strlen(topic) == 17) { // correct topic length ?

    DID = (topic[15] - '0') * 10 + topic[16] - '0'; // extract device ID from MQTT topic

    payload[length] = '\0'; // terminate string with '0'

    String strPayload = String((char*)payload); // convert to string

    if (length == 0) {

      error = 2; // no payload sent

    } else {

      if (DID == 0) {

        device0 = true;

      }

      if (DID == 1) {

        device1 = true;

      }

      if (DID == 2) {

        device2 = true;

      }

      if (DID == 99) {

        device99 = true;

      }

    }
  }
}

Mã ở đây là chỉ nhìn vào 2 chữ số cuối cùng (DID = Device ID) và sau đó đặt cờ (device0 -device 99) có thể được kiểm tra trong vòng lặp chính , tùy thuộc vào nội dung của biến chuỗi 'strPayload'.

Xem ví dụ về MQTT trên github

http://github.com/computourist

Nguồn : https://arduinodiy.wordpress.com

Dịch :Abel Nguyen