此部分内容将介绍如何通过NodeMCU能力实现设备连接阿里云物联网平台,开发板与云平台通过标准MQTT进行连接,并实现Topic上行数据上报以及下行消息监听。
安装软件库
看过往期教程大概都知道,连接上阿里云物联网平台需要实现以下三部分:
- 设备可以上网
- 支持MQTT协议栈(可用MQTT收发消息)
- 若使用高级版Alink协议需要封装JSON数据(或使用高级版透传编写数据转换脚本在平台封装JSON数据)
为此,Arduino IDE中集成以上后1种软件库(ESP8266.h),剩下2个只需下载安装即可
他们分别是:PubSubClient,ArduinoJson,点击项目->加载库->管理库 进行库的搜索与安装。
注意ArduinoJson安装5.13.3版本(6.0版本需要对下面代码修改)
修改库
阿里云物联网平台对MQTT连接条件进行了要求,修改PubSubClient.h库内容,将最大包长MQTT_MAX_PACKET_SIZE改为512,保活时间MQTT_KEEPALIVE改为60。(修改完成不要忘记保存)
示例代码
连接上云示例代码如下,可参考注释理解:
#include <ESP8266WiFi.h>#include <PubSubClient.h>#include <ArduinoJson.h>#define SENSOR_PIN 13/* 连接您的WIFI SSID和密码,这个9个设备可以一致 */#define WIFI_SSID "yourssid"#define WIFI_PASSWD "yourwifipassword"/* 产品的三元组信息,根据9个测试设备的三元组,每个设备都烧录不同的*/#define PRODUCT_KEY "yourproductKey"#define DEVICE_NAME "yourdeviceName"#define DEVICE_SECRET "yourdeviceSecret"/* LD线上环境域名和端口号,不需要改 */#define MQTT_SERVER PRODUCT_KEY".iot-as-mqtt.cn-shanghai.aliyuncs.com"#define MQTT_PORT 1883#define MQTT_USRNAME DEVICE_NAME "&" PRODUCT_KEY// TODO: MQTT连接的签名信息,哈希加密请以"clientIdtestdeviceName"+设备名称+"productKey"+设备模型标识+“timestamp123456789”前往http://tool.oschina.net/encrypt?type=2进行加密//clientId可以随意命名,以下为test,加密生成MQTT_PASSWD的秘钥为设备的deviceSecret// HMACSHA1_SRC clientIdtestdeviceNamehuman04productKeya1rezUVs103timestamp123456789#define CLIENT_ID "test|securemode=3,timestamp=123456789,signmethod=hmacsha1|"#define MQTT_PASSWD "e0748281f8db36e12cac478801318f95ae821ba7"#define ALINK_BODY_FORMAT "{\"id\":\"123\",\"version\":\"1.0\",\"method\":\"%s\",\"params\":%s}"#define ALINK_TOPIC_PROP_POST "/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/event/property/post"#define ALINK_TOPIC_PROP_POSTRSP "/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/event/property/post_reply"#define ALINK_TOPIC_PROP_SET "/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/service/property/set"#define ALINK_METHOD_PROP_POST "thing.event.property.post"unsigned long lastMs = 0;WiFiClient espClient;PubSubClient client(espClient);//下行消息的回调函数void callback(char *topic, byte *payload, unsigned int length){Serial.print("Message arrived [");Serial.print(topic);Serial.print("] ");payload[length] = '\0';Serial.println((char *)payload);if (strstr(topic, ALINK_TOPIC_PROP_SET)){StaticJsonBuffer<100> jsonBuffer;JsonObject& root = jsonBuffer.parseObject(payload);if (!root.success()){Serial.println("parseObject() failed");return ;}}}//wifi初始化连接函数void wifiInit(){WiFi.mode(WIFI_STA);WiFi.begin(WIFI_SSID, WIFI_PASSWD);while (WiFi.status() != WL_CONNECTED){delay(1000);Serial.println("WiFi not Connect");}Serial.println("Connected to AP");Serial.println("IP address: ");Serial.println(WiFi.localIP());client.setServer(MQTT_SERVER, MQTT_PORT); /* 连接WiFi之后,连接MQTT服务器 */client.setCallback(callback);}//mqtt连接检查函数void mqttCheckConnect(){while (!client.connected()){Serial.println("Connecting to MQTT Server ...");if (client.connect(CLIENT_ID, MQTT_USRNAME, MQTT_PASSWD)){Serial.println("MQTT Connected!");// client.subscribe(ALINK_TOPIC_PROP_POSTRSP);client.subscribe(ALINK_TOPIC_PROP_SET);Serial.println("subscribe done");}else{Serial.print("MQTT Connect err:");Serial.println(client.state());delay(5000);}}}//mqtt循环发送内容void mqttIntervalPost(){char param[32];char jsonBuf[128];sprintf(param, "{\"MotionAlarmState\":%d}", digitalRead(13));sprintf(jsonBuf, ALINK_BODY_FORMAT, ALINK_METHOD_PROP_POST, param);Serial.println(jsonBuf);client.publish(ALINK_TOPIC_PROP_POST, jsonBuf);}//初始化函数void setup(){pinMode(SENSOR_PIN, INPUT);/* initialize serial for debugging */Serial.begin(115200);Serial.println("Demo Start");wifiInit();}//主函数// the loop function runs over and over again forevervoid loop(){if (millis() - lastMs >= 5000){lastMs = millis();mqttCheckConnect();/* 上报 */mqttIntervalPost();}client.loop();if (digitalRead(SENSOR_PIN) == HIGH){Serial.println("Motion detected!");delay(2000);}else {Serial.println("Motion absent!");delay(2000);}}
生成连接秘钥信息
并非所有的MQTT认证都会被阿里云物联网平台的MQTT服务器通过,终端需要有以下三个信息完成设备合法性认证:
- MQTT_USRNAME:DEVICE_NAME “&” PRODUCT_KEY组成
- CLIENT_ID:test|securemode=3,timestamp=123456789,signmethod=hmacsha1|,前面的test可以任意
- MQTT_PASSWD:clientId+设备clientId+deviceName+设备名称+productKey+设备模型标识+timestamp123456789组成的字符串以设备的deviceSecret为秘钥,在CLIENT_ID最后那个signmethod指定的加签加密算法下生成的字符串。
在线加密地址:http://tool.oschina.net/encrypt?type=2
例如client为hello,三元组分别为:
- deviceName:deviceName
- productKey:productKey
- deviceSecret:deviceSecret
- MQTT_USRNAME = DEVICE_NAME “&” PRODUCT_KEY = deviceName&productKey
- CLIENT_ID:hello|securemode=3,timestamp=123456789,signmethod=hmacsha1|
- MQTT_PASSWD:207e5076a0918b8bfe0ee5755e77e3cc4df572d0

补充参考材料
https://www.yuque.com/cloud-dev/iot-tech/mebm5g
连接阿里云平台
在阿里云物联网平台新建任意设备模型的产品,在产品下新建设备,将三元组生成信息修改至NodeMCU代码,编译下载运行,设备正常上线,并可通过在线调试获取到设备上报的原始信息。
Arduino代码解读
在setup()初始化函数中,首先设定了13号引脚GPIO13->D7引脚的INPUT模式(引脚映射对应下图),设置串口通讯波特率为115200(串口通讯速率),调用前面定义的wifi初始化函数(持续连接直到连接上为止)。
在loop()函数中,每5秒进行一次MQTT消息上报,并将IO口内容读取到通过串口打印出来。
void loop(){if (millis() - lastMs >= 5000){lastMs = millis();mqttCheckConnect();Serial.println("start once!");/* 上报 */mqttIntervalPost();}client.loop();if (digitalRead(SENSOR_PIN) == HIGH){Serial.println("Motion detected!");delay(2000);}else {Serial.println("Motion absent!");delay(2000);}}
mqttCheckConnect()函数负责对MQTT连接性进行检查,并在第一次连接或掉线后连接MQTT服务器,在连接成功后订阅了ALINK_TOPIC_PROP_SETTopic,以监听从服务器下行的属性set消息,若连接失败则5秒后重试。
//mqtt连接检查函数void mqttCheckConnect(){while (!client.connected()){Serial.println("Connecting to MQTT Server ...");if (client.connect(CLIENT_ID, MQTT_USRNAME, MQTT_PASSWD)){Serial.println("MQTT Connected!");// client.subscribe(ALINK_TOPIC_PROP_POSTRSP);client.subscribe(ALINK_TOPIC_PROP_SET);Serial.println("subscribe done");}else{Serial.print("MQTT Connect err:");Serial.println(client.state());delay(5000);}}}
与平台数据交互部分在mqttIntervalPost()函数中进行,通过sprintf函数对数据进行封装和拼接,再将消息发送到属性上报Topic ALINK_TOPIC_PROP_POST,拼接的数据格式会在串口监视器打印出来。
{"id":"123","version":"1.0","method":"thing.event.property.post","params":{"MotionAlarmState":1}}
//mqtt循环发送内容void mqttIntervalPost(){char param[32];char jsonBuf[128];sprintf(param, "{\"MotionAlarmState\":%d}", digitalRead(13));sprintf(jsonBuf, ALINK_BODY_FORMAT, ALINK_METHOD_PROP_POST, param);Serial.println(jsonBuf);client.publish(ALINK_TOPIC_PROP_POST, jsonBuf);}
关于PubSubClient.h包API内容可以参见https://pubsubclient.knolleary.net/api.html

