Esp32+阿里云物联网平台+DHT11+控制LED(基于AsyncMqttClient库)
关于本文: 本文是上一篇“Esp32+阿里云物联网平台+DHT11+控制LED(基于PubSubClient库)”的拓展篇。如果对阿里云物联网平台入门开发感兴趣欢迎前往上篇查看更详细的内容,本文是基于AsyncMqttClient库的另一种代码实现,对于硬件部分连接、阿里云物联网平台介绍以及参数设置这些基础内容请看上一篇,这里不做赘述。
一、开发环境准备
- Arduino IDE
- 安装库:
- AsyncMqttClient:整体MQTT协议操作
- DHT sensor :获取温湿度
- ArduinoJson:json数据操作
- Crypto :SHA256加密密码
说明:为了使用方便,自行参照AsyncMqttClient源码改写了一个库来代替AsyncMqttClient连接MQTT ,你看到的本文包含头文件是:
#include "AliyunMqtt.h"
#include "DHT.h"
- 注册并登录物联网平台
二、代码实现
//宏定义发布/订阅主题
#define ALINK_BODY_FORMAT "{\"id\":\"TempHumi\",\"version\":\"1.0\",\"method\":\"thing.event.property.post\",\"params\":%s}"
#define ALINK_TOPIC_PROP_POST "/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/event/property/post"
#define ALINK_TOPIC_PROP_SET "/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/service/property/set"
//WiFi连接函数
void connectToWifi() {
Serial.println("Connecting to Wi-Fi...");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
WiFi.onEvent(WiFiEvent);
while (WL_CONNECTED != WiFi.status()) { // 判断WIFI的连接状态
delay(500);
#if debugState
Serial.print(".");
#endif
}
}
//MQTT连接函数
void checkMqttAndReconnect() {
static uint8_t mqttConnectStateMachine = 0; // MQTT连接的状态机
static uint32_t nowTime = 0; // 检查时间
switch (mqttConnectStateMachine) {
// 检查MQTT的连接状态
case 0:
{
if (MqttConnectState == false) { // 如果当前MQTT的连接状态为已断开连接
mqttClient.disconnect(true); // 断开之前的连接
mqttClient.connect(); // 连接MQTT服务器
mqttConnectStateMachine = 1; // MQTT连接中,状态机=1,重连中
nowTime = millis(); // 更新检查时间
#if debugState
Serial.println("阿里云MQTT服务器已断开连接!开始尝试重连中......");
#endif
}
}
break;
// MQTT重连中
case 1:
{
if ((MqttConnectState == true) || millis() - nowTime > ckeckMqttConnectTime) { // 如果当前MQTT的连接状态为已连接或者超过重连时间
mqttConnectStateMachine = 0; // 状态机=0,回到检查中
}
}
break;
default:
break;
}
}
/**
函数功能:WiFi库监听连接状态事件
默认参数:event :0/1/2/3/4/5/6/7
SYSTEM_EVENT_STA_GOT_IP:连接上WIFI后获得IP地址
SYSTEM_EVENT_STA_DISCONNECTED:未成功连接WiFi标志
*/
void WiFiEvent(WiFiEvent_t event) {
Serial.printf("[WiFi-event] event: %d\n", event);
switch (event) {
case SYSTEM_EVENT_STA_GOT_IP:
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
Serial.println("WiFi lost connection");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
break;
}
}
/**
断开mqtt回调,查看断开连接的原因
*/
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
Serial.println("Disconnected from MQTT.");
MqttConnectState = false; // 当前MQTT的连接状态为已断开连接
switch (reason) {
case AsyncMqttClientDisconnectReason::TCP_DISCONNECTED:
Serial.println("Reason: TCP disconnected");
break;
case AsyncMqttClientDisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
Serial.println("Reason: MQTT unacceptable protocol version");
break;
case AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED:
Serial.println("Reason: MQTT identifier rejected");
break;
case AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE:
Serial.println("Reason: MQTT server unavailable");
break;
case AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS:
Serial.println("Reason: MQTT malformed credentials");
break;
case AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED:
Serial.println("Reason: MQTT not authorized");
break;
default:
Serial.println("Reason: Unknown reason");
break;
}
}
/**
连接mqtt成功回调
*/
void onMqttConnect(bool sessionPresent) {
Serial.println("Connected to MQTT.");
Serial.print("Session present: ");
Serial.println(sessionPresent);
MqttConnectState = true;
}
//读取温湿度数据并形成数据链接字符串,被主题发布函数使用
void getTempHumi() {
if (MqttConnectState == true) {
TempValue = dht.readTemperature();
HumiValue = dht.readHumidity();
#if debugState
Serial.print("TempValue:");
Serial.print(TempValue);
Serial.print("--HumiValue:");
Serial.println(HumiValue);
#endif
if (!isnan(TempValue) && !isnan(HumiValue)) {
// 格式化温湿度数据,保留指定的小数位数
String temperatureStr = String(TempValue, 2);
String humidityStr = String(HumiValue, 1);
snprintf(payload, sizeof(payload), "{\"RoomTemp\":%s,\"RoomHumidity\":%s}", temperatureStr.c_str(), humidityStr.c_str());
} else {
Serial.println("Failed to read temperature and humidity data.");
}
}
}
/**
发布温湿度主题
*/
void TaskTempHumi() {
mqttClient.publish(ALINK_TOPIC_PROP_POST, 0, true, payload);
Serial.println("Published temperature and humidity data.");
}
/*
char* topic: 表示接收到的消息所属的主题。
char* payload: 表示接收到的消息的内容,以字符串形式表示。
AsyncMqttClientMessageProperties properties: 表示接收到的消息的属性,包括 QoS、重复标志(dup)、保留标志(retain)等。
size_t len: 表示接收到的消息的长度。
size_t index: 如果消息被分片发送,则 index 表示当前分片的索引。
size_t total: 如果消息被分片发送,则 total 表示消息的总分片数。
订阅主题后得到的内容为:{\"id\":\"1\",\"version\":\"1.0\",\"params\":{\"LightSwitch\":0}},
*/
void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
#if debugState
Serial.println("Publish received.");
Serial.print(" topic: ");
Serial.println(topic);
#endif
// 解析 JSON 数据
DynamicJsonDocument doc(512); // JSON 文档
deserializeJson(doc, payload);
// 检查是否包含LightSwitch
if (doc.containsKey("params") && doc["params"].containsKey("LightSwitch")) {
// 提取 LightSwitch 的值
lightSwitch = doc["params"]["LightSwitch"];
if (lightSwitch == 0) {
// 灯关闭状态
digitalWrite(LED_PIN, LOW);
} else if (lightSwitch == 1) {
// 灯打开状态
digitalWrite(LED_PIN, HIGH);
}
}
}
//setup和loop函数
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println();
dht.begin();
pinMode(LED_PIN, OUTPUT);
pinMode(DHTPIN, INPUT);
WiFi.disconnect(true); // 断开当前WIFI连接
WiFi.mode(WIFI_STA); // 设置WIFI模式为STA模式
connectToWifi(); // 开始连接WIFI
/*
setDeviceCertificate:封装的关键函数调用,用于连接MQTT的验证
传入参数:产品KEY,设备名称,设备秘钥
*/
aliyunMqtt.setDeviceCertificate(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
mqttClient.setKeepAlive(60);
mqttClient.setMaxTopicLength(1024);//设置最大消息长度
mqttClient.connect();
mqttClient.onConnect(onMqttConnect);
mqttClient.onDisconnect(onMqttDisconnect);
mqttClient.onPublish(onMqttPublish);
mqttClient.onSubscribe(onMqttSubscribe);//订阅主题成功的回调
mqttClient.onUnsubscribe(onMqttUnsubscribe);//取消订阅主题的回调
mqttClient.onMessage(onMqttMessage);
}
void loop() {
if ((WL_CONNECTED == WiFi.status()) && (MqttConnectState == false)) checkMqttAndReconnect();
getTempHumi();
}
//AliyunMqtt.c
#include "AliyunMqtt.h"
AsyncMqttClient mqttClient; // 实例化mqttClient
// AliyunMqtt::AliyunMqtt(){}
/**
* 函数功能:SHA256加密
* 参数1:[_signContent] [const String&] 签名内容
* 参数2:[_deviceSecret] [const String&] 设备密钥
* 返回值:[String] 加密后的字符串
* 注意事项:无
*/
String AliyunMqtt::hmac256(String _signContent, String _deviceSecret) {
uint8_t hashCode[sha256HmacSize] = {0};
SHA256 sha256;
const char* key = _deviceSecret.c_str();
size_t keySize = _deviceSecret.length();
sha256.resetHMAC(key, keySize);
sha256.update(_signContent.c_str(), _signContent.length());
sha256.finalizeHMAC(key, keySize, hashCode, sizeof(hashCode));
String sign = "";
for (uint8_t i = 0; i < sha256HmacSize; i++) {
sign += "0123456789ABCDEF"[hashCode[i] >> 4];
sign += "0123456789ABCDEF"[hashCode[i] & 0xf];
}
return sign;
}
void AliyunMqtt::setDeviceCertificate(String _productKey, String _deviceName, String _deviceSecret, String _region,uint16_t _mqttPort) {
String timestamp = String(millis()); // 获取设备上电的时间戳
char signContent[512] = {0}; // 定义签名字符串变量
sprintf(clientId, "%s|securemode=3,signmethod=hmacsha256,timestamp=%s|", _deviceName.c_str(), timestamp.c_str()); // 拼接客户端id
sprintf(signContent, "clientId%sdeviceName%sproductKey%stimestamp%s", _deviceName.c_str(), _deviceName.c_str(), _productKey.c_str(), timestamp.c_str()); // 拼接签名内容
hmac256(signContent, _deviceSecret).toCharArray(mqttPassword, sizeof(mqttPassword)); // MQTT密码进行SHA256加密
sprintf(mqttUsername, "%s&%s", _deviceName.c_str(), _productKey.c_str());
sprintf(domain, "%s.iot-as-mqtt.%s.aliyuncs.com", _productKey.c_str(), _region.c_str());
mqttClient.setServer(domain, _mqttPort);
mqttClient.setClientId(clientId); // 设置clientId
mqttClient.setCredentials(mqttUsername, mqttPassword); // 设置MQTT用户名和密码
}
三、效果
在阿里云物联网平台“在线调试”,将标识“LightSwitch”的开关打开,设置之后能看到连接的LED1秒内点亮。
总结:
AsyncMqttClient采用函数回调非阻塞方式实现异步消息处理,而PubSubClient执行 MQTT 操作时会阻塞其他任务。AsyncMqttClient基于 ArduinoAsync 库构建,消息功能实现更丰富完善,源码例程清晰,虽然测试过程繁琐费时,本测试代码实现部分也花巨资参考了B站某个up,改写成自己的AliyunMqtt库,总体来说入门就选择PubSubClient更快实现,想掌握原理和达到更高性能就选AsyncMqttClient。