利用PubSubClient库(2.8.0版本),可以快速实现MQTT数据包封装,将ESP32-C3作为MQTT Client以连接MQTT Server。本篇连接的是OneNET的MQTT产品,选取平台的旧版MQTT接入。
图10-1 PubSubClient库
案例在上篇基础之上完成,这里只展示添加的代码部分。
为了验证MQTT的双向通信能力,启用Beetle ESP32-C3的板载LED(连接GPIO10),可以通过OneNET平台发送命令来控灯,这里设计“L0”熄灭,“L1”点亮。
OneNET MQTT旧版接入地址为:183.230.40.39,端口为6002,接入是需要配置“设备ID(即MQTT协议的Client ID)”、“产品ID(即MQTT协议的用户名)”和“鉴权信息(即MQTT协议的密码)”。
另外,OneNET上传数据是统一向平台发布主题为“$dp”的消息,而消息载荷(PayLoad)有平台定义的格式,本例采用格式3——一种JSON对象格式,如:{"temp":29,"humi":26}。
//增加启用LED的相关宏定义
#define LED 10
#define LED_ON digitalWrite(LED, HIGH)
#define LED_OFF digitalWrite(LED, LOW)
//增加OneNET MQTT 信息
const char *mqtt_server = "183.230.40.39"; //onenet 的 IP地址
#define mqtt_devid "deviceid" //设备ID需在OneNET注册
#define mqtt_pubid "productid" //产品ID需在OneNET注册
#define mqtt_password "authorization" //鉴权信息建立产品时自定义
WiFiClient espClient; //tcp client实例用于连接OneNET服务器
PubSubClient client(espClient); //pubsub client实例
char msg_buf[200]; //发送信息的缓存
char dataTemplate[] = "{\"temp\":%d,\"humi\":%d}";//封装格式3的模板
char msgJson[75]; //存放封装好的JSON字串
unsigned short json_len = 0; //JSON字串长度
void sendTempAndHumi(void);
void clientReconnect(void);
//-----get OneNET order callback
void callback(char *topic, byte *payload, unsigned int length) {
Serial.println("message rev:");
Serial.println(topic);
for(size_t i=0; i<length; i++) Serial.print((char)payload[i]);
Serial.println();
if(payload[0] == 'L') {
if(payload[1] == 0x30) //第二个接收字符是0,则接收“L0”,熄灯
LED_OFF;
else if(payload[1] == 0x31) //第二个接收字符是1,则接收“L1”,点灯
LED_ON;
}
}
//-----send data to OneNET
void sendTempAndHumi(void) {
if(client.connected()) {
//为了省事,温湿度只取整数部分,这样可以保证snprintf()转字串成功
int temperature = (int)aht_temp.temperature;
int humidity = (int)aht_humi.relative_humidity;
//将湿度数据套入dataTemplate模板中, 生成的字符串传给msgJson
snprintf(msgJson, 40, dataTemplate, temperature, humidity);
//msgJson的长度
json_len = strlen(msgJson);
//payload首字迹是OneNET格式编号,这里为格式3
msg_buf[0] = char(0x03);
//数据第二位是要发送的数据长度的高八位,数据第三位是要发送数据的长度的低八位
msg_buf[1] = char(json_len >> 8);
msg_buf[2] = char(json_len & 0xff);
//从msg_buf的第四位开始,放入要传的数据msgJson
memcpy(msg_buf + 3, msgJson, strlen(msgJson));
//添加一个0作为最后一位, 这样要发送的msg_buf准备好了
msg_buf[3 + strlen(msgJson)] = 0;
Serial.print("public message:");
Serial.println(msgJson);
//发送数据到主题$dp
client.publish("$dp", (uint8_t *)msg_buf, 3 + strlen(msgJson));
}
}
//重连函数, 如果客户端断线,可以通过此函数重连
void clientReconnect(void) {
while(!client.connected()) {
Serial.println("reconnect MQTT...");
if(client.connect(mqtt_devid, mqtt_pubid, mqtt_password)) {
Serial.println("connected");
} else {
Serial.println("failed");
Serial.println(client.state());
Serial.println("try again in 5 sec");
delay(5000);
}
}
}
//setup()中增加LED初始化和MQTT接入
void setup() {
//省略其它代码
pinMode(LED, OUTPUT);
LED_OFF;
//-----connect to OneNET
//设置客户端连接的服务器,连接Onenet服务器, 使用6002端口
client.setServer(mqtt_server, 6002);
//客户端连接到指定的产品的指定设备.同时输入鉴权信息
client.connect(mqtt_devid, mqtt_pubid, mqtt_password);
//注册回调函数
client.setCallback(callback);
}
//loop()中增加发送
void loop() {
//省略其它代码
//-----update weather info per 5 minutes
if(t.tm_min%5==0 && t.tm_sec==0) {
getAHT10();
if(WiFi.status() == WL_CONNECTED) {
getWeather();
}
sendTempAndHumi(); //此处增加发送温湿度调用
}
//省略其它代码
}
平台发送的命令后,串口可以看到解析,LED也会有变化。命令解析就是依靠接受回调callback(),PubSubClient库对接收数据包自动解析,得到了主题topic和载荷payload。OneNET发来命令的主题是“$crep”再加上一个uuid,而命令就是payload,因为解析的命令是byte数组,为了省事也没有做String类转换,自定义的命令就是“L0”和“L1”也比较简单,逐字节比对就好了。
图10-2 解析命令的串口输出