MQTT库:PubSubClient
连接MQTT服务端
#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
const char *ssid = "613专属";
const char *password = "613613613";
const char *mqttServer = "test.ranye-iot.net"; // MQTT服务器地址
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
void connectMQTTServer()
{
// 根据ESP32的MAC地址生成客户端ID(避免与其它esp32的客户端ID重名)
String clientId = "esp32-" + WiFi.macAddress();
// 连接MQTT服务器
if (mqttClient.connect(clientId.c_str()))
{
Serial.println("MQTT Server Connected.");
Serial.println("Server Address: ");
Serial.println(mqttServer);
Serial.println("ClientId:");
Serial.println(clientId);
}
else
{
Serial.print("MQTT Server Connect Failed. Client State:");
Serial.println(mqttClient.state()); // returnCode--连接返回码
delay(3000);
}
}
void connectWifi()
{
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) // 等待WiFi连接,成功连接后输出成功信息
{
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi Connected!");
Serial.println("");
}
void setup()
{
Serial.begin(115200);
WiFi.mode(WIFI_STA); // 设置ESP32工作模式
connectWifi();
mqttClient.setServer(mqttServer, 1883); // 设置MQTT服务器和端口号
connectMQTTServer(); // 连接MQTT服务器
}
void loop()
{
if (mqttClient.connected())
{ // 如果开发板成功连接服务器
mqttClient.loop(); // 保持客户端心跳,在连接状态下不要使用delay()
}
else
{ // 如果开发板未能成功连接服务器
connectMQTTServer(); // 则尝试连接服务器
}
}
发布、订阅和取消订阅
MQTT报文:
PUBLISH--发布信息
SUBSCRIBE--订阅主题
SUBACK--订阅主题
UNSUBSCRIBE--取消订阅
PUBLISH--发布信息
MQTT客户端一旦连接到服务端,便可以发布信息,每条发布的MQTT消息必须包含一个主题,MQTT服务器可以通过主题确定将信息转发给那些服务器。
PUBLISH报文:
- topicName--主题名:用于识别此信息应发布到哪一个主题
- QoS(Quality of Service)--服务质量等级:QoS有三个级别:0、1和2。QoS决定MQTT通讯有什么样的服务保证。
- packetId--报文标识符:报文标识符的内容与QoS级别有密不可分的关系。只有QoS级别大于0时,报文标识符才是非零数值。如果QoS等于0,报文标识符为0。
- retainFlag--保留标志:在默认情况下,当客户端订阅了某一主题后,并不会马上接收到该主题的信息。只有在客户端订阅该主题后,服务端接收到该主题的新信息时,服务端才会将最新接收到的该主题信息推送给客户端。但是在有些情况下,我们需要客户端在订阅了某一主题后马上接收到一条该主题的信息。这时候就需要用到保留标志这一信息。
- payload--有效载荷:有效載荷是我们希望通过MQTT所发送的实际内容。我们可以使用MQTT协议发送文本,图像等格式的内容。这些内容都是通过有效載荷所发送的。
- dupFlag--重发标志:当MQTT报文的接收方没有及时确认收到报文时,发送方会重复发送MQTT报文。在重复发送MQTT报文时,发送方会将此“重发标志”设置为true。请注意,重发标志只在QoS级别大于0时使用。
SUBSCRIBE--订阅主题
当客户端连接到服务端后,除了可以发布信息,也可以接收信息,客户端要想订阅主题,首先要向服务端发送主题订阅请求。一个SUBSCRIBE报文可以用于订阅一个或者多个主题。
SUBACK--订阅确认
服务端接收到客户端的订阅报文后,会向客户端发送SUBACK报文确认订阅。SUBACK报文包含有“订阅返回码”和“报文标识符”这两个信息。
返回码 | Return Code Response |
---|---|
0 | 订阅成功 – QoS 0 |
1 | 订阅成功- QoS 1 |
2 | 订阅成功- QoS 2 |
128 | 订阅失败 |
UNSUBSCRIBE--取消订阅
当客户端要取消订阅某主题时,可通过向服务端发送UNSUBSCRIBE – 取消订阅报文来实现。
ESP32发布MQTT消息
注意:
// 建立发布主题。主题名称以Taichi-Maker-为前缀,后面添加设备的MAC地址。
// 这么做是为确保不同用户进行MQTT信息发布时,ESP8266客户端名称各不相同,
String topicString = "Taichi-Maker-Pub-" + WiFi.macAddress(); // 订阅主题
char publishTopic[topicString.length() + 1]; // 把字符串变量转换为字符数组,因为publish只能接收字符数组类型的数据
// 字符串数组后面多了个结束符\0,所以长度要+1
strcpy(publishTopic, topicString.c_str());// .c_str()的作用是把字符串转换为char类型
#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <Ticker.h>
const char *ssid = "613专属";
const char *password = "613613613";
const char *mqttServer = "test.ranye-iot.net"; // MQTT服务器地址
Ticker ticker;
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
int count; // Ticker计数用变量
void connectMQTTServer()
{
// 根据ESP32的MAC地址生成客户端ID(避免与其它esp32的客户端ID重名)
String clientId = "esp32-" + WiFi.macAddress();
// 连接MQTT服务器
if (mqttClient.connect(clientId.c_str()))
{
Serial.println("MQTT Server Connected.");
Serial.println("Server Address: ");
Serial.println(mqttServer);
Serial.println("ClientId:");
Serial.println(clientId);
}
else
{
Serial.print("MQTT Server Connect Failed. Client State:");
Serial.println(mqttClient.state()); // returnCode--连接返回码
delay(3000);
}
}
void connectWifi()
{
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) // 等待WiFi连接,成功连接后输出成功信息
{
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi Connected!");
Serial.println("");
}
void tickerCount()
{
count++;
}
// 发布信息
void pubMQTTmsg()
{
static int value; // 客户端发布信息用数字,静态变量的作用域相当于全局变量
// 建立发布主题。主题名称以Taichi-Maker-为前缀,后面添加设备的MAC地址。
// 这么做是为确保不同用户进行MQTT信息发布时,ESP8266客户端名称各不相同,
String topicString = "Taichi-Maker-Pub-" + WiFi.macAddress(); // 订阅主题
char publishTopic[topicString.length() + 1]; // 把字符串变量转换为字符数组,因为publish只能接收字符数组类型的数据
// 字符串数组后面多了个结束符\0,所以长度要+1
strcpy(publishTopic, topicString.c_str()); // .c_str()的作用是把字符串转换为char类型
// 建立发布信息。信息内容以Hello World为起始,后面添加发布次数。
String messageString = "Hello World " + String(value++);
char publishMsg[messageString.length() + 1];
strcpy(publishMsg, messageString.c_str());
// 实现ESP8266向主题发布信息
if (mqttClient.publish(publishTopic, publishMsg))
{
Serial.println("Publish Topic:");
Serial.println(publishTopic);
Serial.println("Publish message:");
Serial.println(publishMsg);
}
else
{
Serial.println("Message Publish Failed.");
}
}
void setup()
{
Serial.begin(115200);
WiFi.mode(WIFI_STA); // 设置ESP32工作模式
connectWifi();
mqttClient.setServer(mqttServer, 1883); // 设置MQTT服务器和端口号
connectMQTTServer(); // 连接MQTT服务器
ticker.(1, tickerCount); // 每隔1s执行一次tickerCount函数
}
void loop()
{
if (mqttClient.connected())
{ // 如果开发板成功连接服务器
// 每隔3秒钟发布一次信息
if (count >= 3)
{
pubMQTTmsg();
count = 0;
}
// 保持心跳
mqttClient.loop();
}
else
{ // 如果开发板未能成功连接服务器
connectMQTTServer(); // 则尝试连接服务器
}
}
MQTT主题进阶
1.主题基本形式;2.主题分级;3.主题通配符;4.主题应用注意事项
1.主题基本形式
主题基本形式:主题的最基本形式就是一个字符串,以下是几个主题示例:
- myTopic
- motorSpeed
- MotorSpeed
- current time
使用主题时需要注意以下几点:
- 主题是区分大小写的,比如motorSpeed和MotorSpeed是两个完全不同的主题
- 主题可以使用空格,例如current time
- 大部分MQTT服务端是不支持中文主题的,所以应使用英文字符串或ASCII字符来作为MQTT主题。
2.主题分级
为了更好的对主题进行管理和分类,我们可以对主题进行分级处理,MQTT主题各个级别之间可以使用"/"来分隔,例:Tyler-1/motor/1/speed
在以上示例中一共有四级主题,分别是第1级 Tyler-1、第2级motor、第三级1、第4级speed。主题的每一级至少需要一个字符。
我们再来看几个分级主题的示例:
home/sensor/kitchen/temperature
home/sensor/kitchen/brightness
home/sensor/bedroom/temperature
home/sensor/bedroom/brightness
3.主题通配符
当客户端订阅主题时,可以使用通配符同时订阅多个主题,通配符只能在订阅主题时使用,两种通配符:单级通配符和多级通配符
(1)单级通配符:+,单级通配符可以代替一个主题级别
例如:home/sensor/+/temperature
当客户端订阅了以上主题后,它将会收到以下主题的信息内容:
home/sensor/kitchen/temperature
home/sensor/bedroom/temperature
在home后面的级别中,由于客户端订阅的主题使用了+ 单级通配符,因此无论home级别后面的内容是什么,客户端都能收到这些主题的信息。
(2)多级通配符:#,可以涵盖任意数量的主题级别。如下示例所示, 多级通配符必须是主题中的最后一个字符。
home/sensor/#
当客户端订阅了以上含有”#”的主题后,可以收到以下主题的信息。
home/sensor/kitchen/temperature
home/sensor/bedroom/brightness
home/sensor/data
多级通配符可以代替多级主题信息,因此无论”home/sensor”后面有一级还是多级主题,都可以被订阅了”home/sensor/#”的客户端接收到。
4.主题应用注意事项
– 以$开始的主题
以$开始的主题是MQTT服务端系统保留的特殊主题,我们不能随意订阅或者向其发布信息。例如:
– 不要用 “/” 作为主题开头
MQTT允许使用“/”作为主题的开头,例如/home/sensor/data。但是这将这么做毫无意义,而且会额外产生一个没有用处的主题级别。
– 主题中不要使用空格
MQTT协议允许我们在主题中使用空格,但是阅读和调试含有空格的主题会显得异常困难。
– 保持主题简洁明了
– 主题中尽量使用ASCII字符
-在主题中嵌入客户端ID
在主题中嵌入发布消息的客户端ID,这一操作可以为开发和管理MQTT信息提供便利。通过主题中的客户端ID内容,我们可以很容易的了解该主题信息是由哪一台设备所发布的。
ESP32订阅MQTT主题
学习内容:1.订阅单个主题;2.订阅多个主题;3.使用单级通配符订阅主题;4.使用多级通配符订阅主题
1.订阅单个主题
执行点灯操作
#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#define LED1 1 // LED测试引脚
const char *ssid = "613专属";
const char *password = "613613613";
const char *mqttServer = "test.ranye-iot.net"; // MQTT服务器地址
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
// 订阅指定主题
void subscribeTopic()
{
String topicString = "Taichi-Maker-Sub-" + WiFi.macAddress();
char subTopic[topicString.length() + 1];
strcpy(subTopic, topicString.c_str());
// 通过串口监视器输出是否成功订阅主题以及订阅的主题名称
if (mqttClient.subscribe(subTopic))
{
Serial.println("Subscrib Topic:");
Serial.println(subTopic);
}
else
{
Serial.print("Subscribe Fail...");
}
}
void connectMQTTServer()
{
// 根据ESP32的MAC地址生成客户端ID(避免与其它esp32的客户端ID重名)
String clientId = "esp32-" + WiFi.macAddress();
// 连接MQTT服务器
if (mqttClient.connect(clientId.c_str()))
{
Serial.println("MQTT Server Connected.");
Serial.println("Server Address: ");
Serial.println(mqttServer);
Serial.println("ClientId:");
Serial.println(clientId);
subscribeTopic(); // 订阅指定主题
}
else
{
Serial.print("MQTT Server Connect Failed. Client State:");
Serial.println(mqttClient.state()); // returnCode--连接返回码
delay(3000);
}
}
void connectWifi()
{
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) // 等待WiFi连接,成功连接后输出成功信息
{
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi Connected!");
Serial.println("");
}
// 收到信息后的回调函数
// 参数1:主题;参数2:主题内容;信息长度
void receiveCallback(char *topic, byte *payload, unsigned int length)
{
Serial.print("Message Received [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++)
{
Serial.print((char)payload[i]); // 接收到的信息内容,传递过来的数据格式为数组
}
Serial.println("");
Serial.print("Message Length(Bytes) ");
Serial.println(length);
if ((char)payload[0] == '1')
{ // 如果收到的信息以“1”为开始
digitalWrite(LED1, HIGH); // 则点亮LED。
Serial.println("LED ON");
}
else
{
digitalWrite(LED1, LOW); // 否则熄灭LED。
Serial.println("LED OFF");
}
}
void setup()
{
Serial.begin(115200);
pinMode(LED1, OUTPUT);
WiFi.mode(WIFI_STA); // 设置ESP32工作模式
connectWifi();
mqttClient.setServer(mqttServer, 1883); // 设置MQTT服务器和端口号
mqttClient.setCallback(receiveCallback); // 设置MQTT订阅回调函数
connectMQTTServer(); // 连接MQTT服务器
}
void loop()
{
if (mqttClient.connected())
{
mqttClient.loop();
}
else
connectMQTTServer();
}
2.订阅多个主题
// 订阅指定主题
void subscribeTopic(){
// 建立订阅主题1。主题名称以Taichi-Maker-Sub为前缀,后面添加设备的MAC地址。
// 这么做是为确保不同设备使用同一个MQTT服务器测试消息订阅时,所订阅的主题名称不同
String topicString = "Taichi-Maker-Sub-" + WiFi.macAddress();
char subTopic[topicString.length() + 1];
strcpy(subTopic, topicString.c_str());
// 建立订阅主题2
String topicString2 = "Taichi-Maker-Sub2-" + WiFi.macAddress();
char subTopic2[topicString2.length() + 1];
strcpy(subTopic2, topicString2.c_str());
// 通过串口监视器输出是否成功订阅主题1以及订阅的主题1名称
if(mqttClient.subscribe(subTopic)){
Serial.println("Subscrib Topic:");
Serial.println(subTopic);
} else {
Serial.print("Subscribe Fail...");
}
// 通过串口监视器输出是否成功订阅主题2以及订阅的主题2名称
if(mqttClient.subscribe(subTopic2)){
Serial.println("Subscrib Topic:");
Serial.println(subTopic2);
} else {
Serial.print("Subscribe Fail...");
}
}
3.使用单级通配符订阅主题
// 订阅指定主题
void subscribeTopic(){
// 建立订阅主题。主题名称以Taichi-Maker-Sub为前缀,后面添加设备的MAC地址。
// 这么做是为确保不同设备使用同一个MQTT服务器测试消息订阅时,所订阅的主题名称不同
String topicString = "Taichi-Maker-Sub-" + WiFi.macAddress()+"/+/data";
char subTopic[topicString.length() + 1];
strcpy(subTopic, topicString.c_str());
// 通过串口监视器输出是否成功订阅主题以及订阅的主题名称
if(mqttClient.subscribe(subTopic)){
Serial.println("Subscrib Topic:");
Serial.println(subTopic);
} else {
Serial.print("Subscribe Fail...");
}
}
4.使用多级通配符订阅主题
// 订阅指定主题
void subscribeTopic(){
// 建立订阅主题。主题名称以Taichi-Maker-Sub为前缀,后面添加设备的MAC地址。
// 这么做是为确保不同设备使用同一个MQTT服务器测试消息订阅时,所订阅的主题名称不同
String topicString = "Taichi-Maker-Sub-" + WiFi.macAddress()+"/sensor/#";
char subTopic[topicString.length() + 1];
strcpy(subTopic, topicString.c_str());
// 通过串口监视器输出是否成功订阅主题以及订阅的主题名称
if(mqttClient.subscribe(subTopic)){
Serial.println("Subscrib Topic:");
Serial.println(subTopic);
} else {
Serial.print("Subscribe Fail...");
}
}
【重要】ESP32同时订阅和发布MQTT信息
#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <Ticker.h>
#define LED1 1 // LED测试引脚
int count; // Ticker计数用变量
const char *ssid = "613专属";
const char *password = "613613613";
const char *mqttServer = "test.ranye-iot.net"; // MQTT服务器地址
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
// 订阅指定主题
void subscribeTopic()
{
String topicString = "Taichi-Maker-Sub-" + WiFi.macAddress();
char subTopic[topicString.length() + 1];
strcpy(subTopic, topicString.c_str());
// 通过串口监视器输出是否成功订阅主题以及订阅的主题名称
if (mqttClient.subscribe(subTopic))
{
Serial.println("Subscrib Topic:");
Serial.println(subTopic);
}
else
{
Serial.print("Subscribe Fail...");
}
}
void connectMQTTServer()
{
// 根据ESP32的MAC地址生成客户端ID(避免与其它esp32的客户端ID重名)
String clientId = "esp32-" + WiFi.macAddress();
// 连接MQTT服务器
if (mqttClient.connect(clientId.c_str()))
{
Serial.println("MQTT Server Connected.");
Serial.println("Server Address: ");
Serial.println(mqttServer);
Serial.println("ClientId:");
Serial.println(clientId);
subscribeTopic(); // 订阅指定主题
}
else
{
Serial.print("MQTT Server Connect Failed. Client State:");
Serial.println(mqttClient.state()); // returnCode--连接返回码
delay(3000);
}
}
void connectWifi()
{
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) // 等待WiFi连接,成功连接后输出成功信息
{
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi Connected!");
Serial.println("");
}
// 收到信息后的回调函数
// 参数1:主题;参数2:主题内容;信息长度
void receiveCallback(char *topic, byte *payload, unsigned int length)
{
Serial.print("Message Received [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++)
{
Serial.print((char)payload[i]); // 接收到的信息内容,传递过来的数据格式为数组
}
Serial.println("");
Serial.print("Message Length(Bytes) ");
Serial.println(length);
if ((char)payload[0] == '1')
{ // 如果收到的信息以“1”为开始
digitalWrite(LED1, HIGH); // 则点亮LED。
Serial.println("LED ON");
}
else
{
digitalWrite(LED1, LOW); // 否则熄灭LED。
Serial.println("LED OFF");
}
}
void setup()
{
Serial.begin(115200);
pinMode(LED1, OUTPUT);
// Ticker定时对象
ticker.attach(1, tickerCount);
WiFi.mode(WIFI_STA); // 设置ESP32工作模式
connectWifi();
mqttClient.setServer(mqttServer, 1883); // 设置MQTT服务器和端口号
mqttClient.setCallback(receiveCallback); // 设置MQTT订阅回调函数
connectMQTTServer(); // 连接MQTT服务器
}
void tickerCount()
{
count++;
}
// 发布信息
void pubMQTTmsg()
{
// 建立发布主题。主题名称以Taichi-Maker-为前缀,后面添加设备的MAC地址。
// 这么做是为确保不同用户进行MQTT信息发布时,ESP8266客户端名称各不相同,
String topicString = "Taichi-Maker-Pub-" + WiFi.macAddress();
char publishTopic[topicString.length() + 1];
strcpy(publishTopic, topicString.c_str());
// 定时向服务器主题发布当前D3引脚状态
String messageString;
if (digitalRead(D3))
{
messageString = "on";
}
else
{
messageString = "off";
}
char publishMsg[messageString.length() + 1];
strcpy(publishMsg, messageString.c_str());
// 实现ESP8266向主题发布信息
if (mqttClient.publish(publishTopic, publishMsg))
{
Serial.println("Publish Topic:");
Serial.println(publishTopic);
Serial.println("Publish message:");
Serial.println(publishMsg);
}
else
{
Serial.println("Message Publish Failed.");
}
}
void loop()
{
if (mqttClient.connected())
{ // 每隔3秒钟发布一次信息
if (count >= 3)
{
pubMQTTmsg();
count = 0;
}
mqttClient.loop();
}
else
connectMQTTServer();
}