此部分内容将介绍如何通过NodeMCU能力实现设备连接阿里云物联网平台,开发板与云平台通过标准MQTT进行连接,并实现Topic上行数据上报以及下行消息监听。

安装软件库

看过往期教程大概都知道,连接上阿里云物联网平台需要实现以下三部分:

  • 设备可以上网
  • 支持MQTT协议栈(可用MQTT收发消息)
  • 若使用高级版Alink协议需要封装JSON数据(或使用高级版透传编写数据转换脚本在平台封装JSON数据)

为此,Arduino IDE中集成以上后1种软件库(ESP8266.h),剩下2个只需下载安装即可
他们分别是:PubSubClientArduinoJson,点击项目->加载库->管理库 进行库的搜索与安装。
注意ArduinoJson安装5.13.3版本(6.0版本需要对下面代码修改)
image.png

image.png

修改库

阿里云物联网平台对MQTT连接条件进行了要求,修改PubSubClient.h库内容,将最大包长MQTT_MAX_PACKET_SIZE改为512,保活时间MQTT_KEEPALIVE改为60。(修改完成不要忘记保存)

image.png
image.png

示例代码

连接上云示例代码如下,可参考注释理解:

  1. #include <ESP8266WiFi.h>
  2. #include <PubSubClient.h>
  3. #include <ArduinoJson.h>
  4. #define SENSOR_PIN 13
  5. /* 连接您的WIFI SSID和密码,这个9个设备可以一致 */
  6. #define WIFI_SSID "yourssid"
  7. #define WIFI_PASSWD "yourwifipassword"
  8. /* 产品的三元组信息,根据9个测试设备的三元组,每个设备都烧录不同的*/
  9. #define PRODUCT_KEY "yourproductKey"
  10. #define DEVICE_NAME "yourdeviceName"
  11. #define DEVICE_SECRET "yourdeviceSecret"
  12. /* LD线上环境域名和端口号,不需要改 */
  13. #define MQTT_SERVER PRODUCT_KEY".iot-as-mqtt.cn-shanghai.aliyuncs.com"
  14. #define MQTT_PORT 1883
  15. #define MQTT_USRNAME DEVICE_NAME "&" PRODUCT_KEY
  16. // TODO: MQTT连接的签名信息,哈希加密请以"clientIdtestdeviceName"+设备名称+"productKey"+设备模型标识+“timestamp123456789”前往http://tool.oschina.net/encrypt?type=2进行加密
  17. //clientId可以随意命名,以下为test,加密生成MQTT_PASSWD的秘钥为设备的deviceSecret
  18. // HMACSHA1_SRC clientIdtestdeviceNamehuman04productKeya1rezUVs103timestamp123456789
  19. #define CLIENT_ID "test|securemode=3,timestamp=123456789,signmethod=hmacsha1|"
  20. #define MQTT_PASSWD "e0748281f8db36e12cac478801318f95ae821ba7"
  21. #define ALINK_BODY_FORMAT "{\"id\":\"123\",\"version\":\"1.0\",\"method\":\"%s\",\"params\":%s}"
  22. #define ALINK_TOPIC_PROP_POST "/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/event/property/post"
  23. #define ALINK_TOPIC_PROP_POSTRSP "/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/event/property/post_reply"
  24. #define ALINK_TOPIC_PROP_SET "/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/service/property/set"
  25. #define ALINK_METHOD_PROP_POST "thing.event.property.post"
  26. unsigned long lastMs = 0;
  27. WiFiClient espClient;
  28. PubSubClient client(espClient);
  29. //下行消息的回调函数
  30. void callback(char *topic, byte *payload, unsigned int length)
  31. {
  32. Serial.print("Message arrived [");
  33. Serial.print(topic);
  34. Serial.print("] ");
  35. payload[length] = '\0';
  36. Serial.println((char *)payload);
  37. if (strstr(topic, ALINK_TOPIC_PROP_SET))
  38. {
  39. StaticJsonBuffer<100> jsonBuffer;
  40. JsonObject& root = jsonBuffer.parseObject(payload);
  41. if (!root.success())
  42. {
  43. Serial.println("parseObject() failed");
  44. return ;
  45. }
  46. }
  47. }
  48. //wifi初始化连接函数
  49. void wifiInit()
  50. {
  51. WiFi.mode(WIFI_STA);
  52. WiFi.begin(WIFI_SSID, WIFI_PASSWD);
  53. while (WiFi.status() != WL_CONNECTED)
  54. {
  55. delay(1000);
  56. Serial.println("WiFi not Connect");
  57. }
  58. Serial.println("Connected to AP");
  59. Serial.println("IP address: ");
  60. Serial.println(WiFi.localIP());
  61. client.setServer(MQTT_SERVER, MQTT_PORT); /* 连接WiFi之后,连接MQTT服务器 */
  62. client.setCallback(callback);
  63. }
  64. //mqtt连接检查函数
  65. void mqttCheckConnect()
  66. {
  67. while (!client.connected())
  68. {
  69. Serial.println("Connecting to MQTT Server ...");
  70. if (client.connect(CLIENT_ID, MQTT_USRNAME, MQTT_PASSWD))
  71. {
  72. Serial.println("MQTT Connected!");
  73. // client.subscribe(ALINK_TOPIC_PROP_POSTRSP);
  74. client.subscribe(ALINK_TOPIC_PROP_SET);
  75. Serial.println("subscribe done");
  76. }
  77. else
  78. {
  79. Serial.print("MQTT Connect err:");
  80. Serial.println(client.state());
  81. delay(5000);
  82. }
  83. }
  84. }
  85. //mqtt循环发送内容
  86. void mqttIntervalPost()
  87. {
  88. char param[32];
  89. char jsonBuf[128];
  90. sprintf(param, "{\"MotionAlarmState\":%d}", digitalRead(13));
  91. sprintf(jsonBuf, ALINK_BODY_FORMAT, ALINK_METHOD_PROP_POST, param);
  92. Serial.println(jsonBuf);
  93. client.publish(ALINK_TOPIC_PROP_POST, jsonBuf);
  94. }
  95. //初始化函数
  96. void setup()
  97. {
  98. pinMode(SENSOR_PIN, INPUT);
  99. /* initialize serial for debugging */
  100. Serial.begin(115200);
  101. Serial.println("Demo Start");
  102. wifiInit();
  103. }
  104. //主函数
  105. // the loop function runs over and over again forever
  106. void loop()
  107. {
  108. if (millis() - lastMs >= 5000)
  109. {
  110. lastMs = millis();
  111. mqttCheckConnect();
  112. /* 上报 */
  113. mqttIntervalPost();
  114. }
  115. client.loop();
  116. if (digitalRead(SENSOR_PIN) == HIGH){
  117. Serial.println("Motion detected!");
  118. delay(2000);
  119. }
  120. else {
  121. Serial.println("Motion absent!");
  122. delay(2000);
  123. }
  124. }

生成连接秘钥信息

并非所有的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


  1. MQTT_USRNAME = DEVICE_NAME “&” PRODUCT_KEY = deviceName&productKey
  2. CLIENT_ID:hello|securemode=3,timestamp=123456789,signmethod=hmacsha1|
  3. MQTT_PASSWD:207e5076a0918b8bfe0ee5755e77e3cc4df572d0

image.png
补充参考材料
https://www.yuque.com/cloud-dev/iot-tech/mebm5g

连接阿里云平台

在阿里云物联网平台新建任意设备模型的产品,在产品下新建设备,将三元组生成信息修改至NodeMCU代码,编译下载运行,设备正常上线,并可通过在线调试获取到设备上报的原始信息。
image.png

Arduino代码解读

在setup()初始化函数中,首先设定了13号引脚GPIO13->D7引脚的INPUT模式(引脚映射对应下图),设置串口通讯波特率为115200(串口通讯速率),调用前面定义的wifi初始化函数(持续连接直到连接上为止)。
NodeMCU上云 - 图7

在loop()函数中,每5秒进行一次MQTT消息上报,并将IO口内容读取到通过串口打印出来。

  1. void loop()
  2. {
  3. if (millis() - lastMs >= 5000)
  4. {
  5. lastMs = millis();
  6. mqttCheckConnect();
  7. Serial.println("start once!");
  8. /* 上报 */
  9. mqttIntervalPost();
  10. }
  11. client.loop();
  12. if (digitalRead(SENSOR_PIN) == HIGH){
  13. Serial.println("Motion detected!");
  14. delay(2000);
  15. }
  16. else {
  17. Serial.println("Motion absent!");
  18. delay(2000);
  19. }
  20. }

mqttCheckConnect()函数负责对MQTT连接性进行检查,并在第一次连接或掉线后连接MQTT服务器,在连接成功后订阅了ALINK_TOPIC_PROP_SETTopic,以监听从服务器下行的属性set消息,若连接失败则5秒后重试。

  1. //mqtt连接检查函数
  2. void mqttCheckConnect()
  3. {
  4. while (!client.connected())
  5. {
  6. Serial.println("Connecting to MQTT Server ...");
  7. if (client.connect(CLIENT_ID, MQTT_USRNAME, MQTT_PASSWD))
  8. {
  9. Serial.println("MQTT Connected!");
  10. // client.subscribe(ALINK_TOPIC_PROP_POSTRSP);
  11. client.subscribe(ALINK_TOPIC_PROP_SET);
  12. Serial.println("subscribe done");
  13. }
  14. else
  15. {
  16. Serial.print("MQTT Connect err:");
  17. Serial.println(client.state());
  18. delay(5000);
  19. }
  20. }
  21. }

与平台数据交互部分在mqttIntervalPost()函数中进行,通过sprintf函数对数据进行封装和拼接,再将消息发送到属性上报Topic ALINK_TOPIC_PROP_POST,拼接的数据格式会在串口监视器打印出来。

  1. {"id":"123","version":"1.0","method":"thing.event.property.post","params":{"MotionAlarmState":1}}
  1. //mqtt循环发送内容
  2. void mqttIntervalPost()
  3. {
  4. char param[32];
  5. char jsonBuf[128];
  6. sprintf(param, "{\"MotionAlarmState\":%d}", digitalRead(13));
  7. sprintf(jsonBuf, ALINK_BODY_FORMAT, ALINK_METHOD_PROP_POST, param);
  8. Serial.println(jsonBuf);
  9. client.publish(ALINK_TOPIC_PROP_POST, jsonBuf);
  10. }

关于PubSubClient.h包API内容可以参见https://pubsubclient.knolleary.net/api.html