在ESP 8266 nodeMCU上运行MQTT
自动接触nodeMCU后发现8266是一个非常好的物联网开发Wi-Fi模块,因此就想把MQTT通讯协议在上面运行起来做些简单的事情。
开发目标
- 将nodeMCU作为一个MQTT的客户端运行。
- 使用PubSubClient这个MQTT协议实现。
- 程序每次启动后先把将芯片设置到STA模式下,并连接指定的WI-FI路由器。
- 连接建立好之后连接指定的MQTT服务器,并注册从服务器接收数据的topic,并定期将数据(uptime)发送到服务器端指定的topic上。
- 上传topic:”MAC地址/uplink”
- 接收topic:”MAC地址/downlink”
- 当收到downlink消息后把数据进行解析并执行,目前只支持’blink’命令。该命令可以把芯片上的LED灯按照指定的次数点亮的简单操作。
程序开发
程序启动设置
程序启动的入口为’setup()‘函数,这个函数做几件事情:
- 设置LED控制PIN模式
- 将WI-FI芯片设置到STA运行模式,并连接Wi-Fi路由器
- 连接MQTT服务器,监听’downlink’消息
程序片段解析:
void setup() {
Serial.begin(BAUD_RATE);
pinMode(LED_PIN, OUTPUT); // 设置LED控制端口
WiFi.disconnect();
WiFi.mode(WIFI_STA); // 设置启动为STA模式
WiFi.setAutoConnect(true);
WiFi.begin(wifi_ssid, wifi_pwd); // 连接Wi-Fi路由器
Serial.printf("Connecting to AP(%s), password(%s)\n", wifi_ssid, wifi_pwd);
while (WL_CONNECTED != WiFi.status()) {
Serial.print(".");
blink(BLINK_SLOWLY);
}
Serial.printf("\nWifi connection is setup!\n");
Serial.printf("MAC: %s, IP: %s\n", WiFi.macAddress().c_str(), WiFi.localIP().toString().c_str());
while (!setup_mqtt_connection()) { // 连接MQTT服务器
Serial.print(".");
blink(BLINK_SLOWLY);
}
}
连接MQTT服务器
这个方法的工作就是建立连接并且注册接收的topic。
bool setup_mqtt_connection()
{
char client_id[CLIENT_ID_LEN];
snprintf(client_id, CLIENT_ID_LEN, "%s", WiFi.macAddress().c_str());
Serial.printf("Client[%s] is connecting MQTT server!\n", client_id);
mqtt_connected = mq_client.connect(client_id); // 连接MQTT服务器
if (!mqtt_connected) {
Serial.println("MQTT connection failure");
return false;
}
mq_client.setCallback(mqtt_callback); // 指定接收downlink消息的处理函数
memset(uplink_topic, 0, TOPIC_LEN);
snprintf(uplink_topic, TOPIC_LEN, "%s/uplink", WiFi.macAddress().c_str());
memset(downlink_topic, 0, TOPIC_LEN);
snprintf(downlink_topic, TOPIC_LEN, "%s/downlink", WiFi.macAddress().c_str());
Serial.printf("Subscribing to topic: %s\n", downlink_topic);
mqtt_connected = mq_client.subscribe(downlink_topic); // 注册接收downlink消息
if (!mqtt_connected) {
Serial.println("MQTT connection failure");
return false;
}
Serial.println("MQTT connection is setup");
return true;
}
接收消息处理
MQTT消息处理函数遵循PubSubClient的接口开发就行了。
void mqtt_callback(char *topic, uint8_t* buffer, unsigned int len) {
blink(BLINK_QUICKLY);
memset(recv_buffer, 0, RECV_BUFFER_LEN);
strncpy(recv_buffer, (char *)buffer, (RECV_BUFFER_LEN<len ? RECV_BUFFER_LEN-1:len));
Serial.printf("Received [topic:%s]:%s\n", topic, (char*)recv_buffer);
parse_cmd((char *)recv_buffer);
}
但这里需要注意’len’这个参数的使用细节。’len’这个参数表明接收到的消息的实际长度,最好在处理函数中将数据复制出来后进行处理。最开始我没有这样处理,结果发现’buffer’中的数据会包含发出数据的信息,研究了一下源代码发现PubSubClient的发出/接收缓冲区是共用的,而且发出/接收后都不会重置。另外需要注意的是这个缓冲区并不大,默认为MQTT_MAX_PACKET_SIZE(128)个子节 。
class PubSubClient {
...
uint8_t buffer[MQTT_MAX_PACKET_SIZE]; // PubSubClient中的数据共用缓冲区
主循环
程序主循环的主要任务就是数据周期发送并处理MQTT消息接收
void loop() {
// send message every 10 second
if (millis() - last_uplink_tick >= UPLINK_INTERVAL) {
memset(send_buffer, 0, SEND_BUFFER_LEN);
snprintf(send_buffer, SEND_BUFFER_LEN, "Client[%s@%s]: uptime:%ld",
WiFi.macAddress().c_str(),
WiFi.localIP().toString().c_str(),
millis());
Serial.printf("Sending: %s\n", send_buffer);
blink(BLINK_QUICKLY);
mq_client.publish(uplink_topic, send_buffer); // 发送数据到MQTT服务器
last_uplink_tick = millis();
}
mq_client.loop();
}
使用方法
编译
我使用Arduino IDE进行开发,这是一个蛮不错的开发环境。不熟悉的人可以参考我另外一篇“使用Arduino IDE进行nodeMCU开发”的blog。
运行
nodeMCU客户端
将程序烧入nodeMCU后每次只要通电程序就会自动运行。启动后程序有以下类似输出。
Connecting to AP(your_wifi_ssid), password(your_wifi_password)
....
Wifi connection is setup!
MAC: A0:20:A6:18:47:F1, IP: 192.168.102.102
Client[A0:20:A6:18:47:F1] is connecting MQTT server!
Subscribing to topic: A0:20:A6:18:47:F1/downlink
MQTT connection is setup
Sending: Client[A0:20:A6:18:47:F1@192.168.102.102]: uptime:21959 <-- 上传数据
Sending: Client[A0:20:A6:18:47:F1@192.168.102.102]: uptime:32036
Sending: Client[A0:20:A6:18:47:F1@192.168.102.102]: uptime:42077
Sending: Client[A0:20:A6:18:47:F1@192.168.102.102]: uptime:52160
Sending: Client[A0:20:A6:18:47:F1@192.168.102.102]: uptime:62233
Received [topic:A0:20:A6:18:47:F1/downlink]:#blink#3# <-- 接收控制命令
Received [topic:A0:20:A6:18:47:F1/downlink]:bli <-- 接收到非法命令
Received unknown command: bli
数据接收端
简单运行可以使用’mosquitto_sub’。命令可以参照下面的写法,需要用’-h’指定你的MQTT服务器地址,用’-t’指定接收的topic,这个topic会在nodeMCU每次运行时在串口输出,nodeMCU的MAC地址也会从串口输出。
mosquitto_sub -h "your_mqtt_server" -t "MAC_ADDRESS/uplink"
我直接是在MQTT服务器上运行数据接收端,因此我实际运行的命令如下:
$ mosquitto_sub -t "A0:20:A6:18:47/uplink"
远程控制端
可以使用’mosquitto_pub’进行远程数据发送实现对nodeMCU的控制。下面的例子可以把LED连续点亮3次。
mosquitto_pub -h "your_mqtt_server" -t "MAC_ADDRESS/downlink" -m "blink#3"
我也是从MQTT服务器端直接发送的控制数据,因此命令可以这样写:
$ mosquitto_pub -t "A0:20:A6:18:47:F1/downlink" -m "#blink#3#"
Demo
本文中程序的完整代码可以在github上面找到。