当前位置:   article > 正文

【教程】ESP32连接华为云IoT平台

esp32连接华为云

目录

1前言

2应用侧接入华为云IoT平台

3必备环境

4使用步骤

4.1华为云IoT平台简介

4.2产品定义

4.3设备定义与注册

4.4ESP32编程接入

4.4.1头文件的包含

4.4.2接入参数以及ESP32WiFi的配置

4.4.3WiFi配置与MQTT连接初始化

4.4.4属性上报

4.4.5接收华为云IoT平台下发命令以及命令响应

原创不易,点个赞或者点个关注激励笔者分享更多优质原创内容吧!

开源万岁!


1前言

        相信很多人和笔者一样,参加各种比赛时,都选择了有关华为IoT平台的命题。可惜全网搜寻一番,发现硬件设备与华为云IoT连接的教程几乎没有,大多数是腾讯云、阿里云或者ONENET平台的。经过自己的摸索,终于连接上华为云IoT平台实现相关功能,所以今天的这一篇教程便是ESP32连接华为云IoT平台的教程。

        相比较于以前做过的STM32+ESP8266实现上华为云,ESP32上云可以说是十分十分简单了。

        首先STM32与8266使用的是串口通信发送AT指令的方案,连接服务器的过程总是不太灵敏,且对于待发送或者接收的JSON包难以编码和解析。

        而ESP32自带WiFi模块,使用其PubSubClient库可以方便的做到与服务器连接以及通信,至于JSON格式的处理可以使用ArduinoJSON库。

2应用侧接入华为云IoT平台

关于应用侧和华为云IoT平台的连接,数据流转等功能的使用教程,笔者的一位朋友已经在着手撰写,后面将在这篇文章更新其文章链接,望大家多多催更:D

此处为更新的文章:

【教程】应用侧连接华为云IoT平台_叫我胡萝北的博客-CSDN博客

3必备环境

Arduino IDE

ESP32

PubSubClient库

ArduinoJSON库(V5和V6的代码语法会有一些差别,笔者使用的是V5版本的库)

这是ArduinoJSON库的官方网站ArduinoJson: Efficient JSON serialization for embedded C++

4使用步骤

4.1华为云IoT平台简介

  • 百度搜索华为云,点击进入华为云
  • 硬件接入华为云IoT需要用到华为云的 产品-> IoT物联网->设备接入IoTDA

科普一下,此处的DA是Device Access的意思,表示设备接入

  • 新手点击免费试用即可

  • 在左侧的边栏中可以看到一系列的选项

 

  • 下面对一些常用的选项进行说明

总览:呈现了平台的接入地址以及一些新手引导,建议新手可以根据引导观看,倒数第四个“产品文档”项也提供了软硬件与华为云IoT平台的交互的详细文档,建议新手仔细阅读

产品:顾名思义,这是定义和查看你拥有的产品的地方

设备:一个产品名下可以拥有很多设备,在这里可以定义设备,并对其参数进行修改或进行命令的下发

监控运维:包含 “在线调试” 和 “消息跟踪” 等功能,在线调试可以进行命令下发,参数设置等操作;而消息跟踪则是跟踪硬件设备一段时间内和平台的交互

4.2产品定义

  • 首先定义一个产品(产品-> 创建产品),协议类型选择MQTT,其他参数随产品而定

  • 然后依次点击产品-> 选择你的产品-> 模型定义-> 自定义模型

PS:这里要来定义产品的服务,比如一个物联网的智能配送车就可以有一个订单服务

而订单服务下面又可以分为很多种属性,比如订单下发的命令,订单当前状态的查看,订单配送的命令 

 

  • 我们填写一下服务的相关信息

  • 添加好服务后,可以在服务下添加该产品的属性和命令参数,这一步完成后,产品就算定义完成啦

4.3设备定义与注册

  • 产品定义好后,我们来定义设备

依次点击设备-> 所有设备-> 注册设备

选择所属的产品

  • 单击确定后出现下面的页面提示设备创建成功,点击保存并关闭,会自动下载设备的"device_id" 和 "secret",保存好这个文件。

 

  •  这样就算注册好设备了,但是注意,此时真实的硬件设备并没有和华为云IoT平台连接,所以会显示未激活状态

4.4ESP32编程接入

4.4.1头文件的包含

这四个头文件都是必须的

  1. #include <Wire.h>
  2. #include <WiFi.h>
  3. #include <PubSubClient.h>
  4. #include <ArduinoJson.h>

4.4.2接入参数以及ESP32WiFi的配置

将其中的参数按照实际情况修改

  1. /*MQTT连接配置*/
  2. /*-----------------------------------------------------*/
  3. const char* ssid = "ESP32连接的WiFi名称";
  4. const char* password = "WiFi密码";
  5. const char* mqttServer = "华为云MQTT接入地址";
  6. const int mqttPort = 1883;
  7. //以下3个参数可以由HMACSHA256算法生成,为硬件通过MQTT协议接入华为云IoT平台的鉴权依据
  8. const char* clientId = "";
  9. const char* mqttUser = "";
  10. const char* mqttPassword = "";
  11. WiFiClient espClient; //ESP32WiFi模型定义
  12. PubSubClient client(espClient);
  13. const char* topic_properties_report = "属性上报topic";
  14. //接收到命令后上发的响应topic
  15. char* topic_Commands_Response = "$oc/devices/设备ID/sys/commands/response/request_id=";
  16. /*******************************************************/

PS:

接入地址

        华为云MQTT接入地址以及端口号1883可以在华为云IoTDA的 总览-> 平台接入地址 查看

鉴权信息

        clientId、mqttUser、mqttPassword是设备接入华为云IoT平台时要验证的鉴权信息,可以通过HMACSHA256算法实时在程序中生成,也可以通过产品文档中提供的参数生成工具直接生成(参数生成工具生成的不校验时间戳)

产品文档_设备连接鉴权_华为云

鉴权信息生成工具_华为云

鉴权信息生成工具的使用

        使用刚刚保存的设备id 和 密钥,填入,生成鉴权信息

topic

至于属性上报和命令响应等topic定义,在华为云IoTDA-> 产品-> 选择要查看的产品-> topic管理处查看

依据要使用的功能在程序中定义不同的topic,注意{device_id}应该整个替换为设备id,大括号也要替换

4.4.3WiFi配置与MQTT连接初始化

代码

  1. /*
  2. * 作用: ESP32的WiFi初始化以及与MQTT服务器的连接
  3. * 参数: 无
  4. * 返回值:无
  5. */
  6. void MQTT_Init()
  7. {
  8. //WiFi网络连接部分
  9. WiFi.begin(ssid, password); //开启ESP32的WiFi
  10. while (WiFi.status() != WL_CONNECTED) { //ESP尝试连接到WiFi网络
  11. delay(3000);
  12. Serial.println("Connecting to WiFi...");
  13. }
  14. Serial.println("Connected to the WiFi network");
  15. //MQTT服务器连接部分
  16. client.setServer(mqttServer, mqttPort); //设置连接到MQTT服务器的参数
  17. client.setKeepAlive (60); //设置心跳时间
  18. while (!client.connected()) { //尝试与MQTT服务器建立连接
  19. Serial.println("Connecting to MQTT...");
  20. if (client.connect(clientId, mqttUser, mqttPassword )) {
  21. Serial.println("connected");
  22. } else {
  23. Serial.print("failed with state ");
  24. Serial.print(client.state());
  25. delay(6000);
  26. }
  27. }
  28. //接受平台下发内容的初始化
  29. client.setCallback(callback); //可以接受任何平台下发的内容
  30. }

4.4.4属性上报

        如果你的产品下定义了属性,那么可以通过ESP32硬件向服务器上报属性。

        比如你的产品上有一LED指示灯,那么在产品中我们可以定义一个服务用于管理灯,该服务下包含一属性,如灯的亮灭状态,灯的颜色。在用户改变灯的亮灭或者颜色等属性后ESP32可以将灯的最新属性数据上报给华为云IoT平台,此即为属性上报。

        在产品-> 选择你的产品-> Topic管理处可以看到属性上报时要public的Topic。

         "{device_id}" 依据你的设备改变。

  • 在上文的代码中,我们已经定义了该属性上报Topic
const char* topic_properties_report = "$oc/devices/Doge1_1/sys/properties/report";

         

属性上报的相关代码

  • 以下是我的一个物联网项目的属性上报代码,读者可以根据自己的需要修改其中的参数

注释里有对代码的详细解释,请仔细阅读注释

  1. /*
  2. * 作用: 垃圾桶容量上报到MQTT服务器任务
  3. * 参数: (int)垃圾桶容量,1代表垃圾桶满了,0代表垃圾桶未满
  4. * 返回值:无
  5. * 命名说明:Capacity:容量
  6. */
  7. void TASK_Capacity_Report(int capacity)
  8. {
  9. //以下部分代码调用了ArduinoJson库将属性上报消息打包为JSON格式
  10. //此部分代码可以通过ArduinoJson库的官方网站直接生成
  11. StaticJsonBuffer<300> JSONbuffer; //定义静态的JSON缓冲区用于存储JSON消息
  12. JsonObject& root = JSONbuffer.createObject();
  13. JsonArray& services = root.createNestedArray("services");
  14. JsonObject& service_1 = services.createNestedObject();
  15. JsonObject& properties_1_1 = service_1.createNestedObject("properties");
  16. service_1["service_id"] = "ash_bin_Service";
  17. properties_1_1["ash_bin_capacity"] = capacity;
  18. // root.prettyPrintTo(Serial);//调试用,将JSON打印到串口
  19. //以下将生成好的JSON格式消息格式化输出到字符数组中,便于下面通过PubSubClient库发送到服务器
  20. char JSONmessageBuffer[100];
  21. root.printTo(JSONmessageBuffer, sizeof(JSONmessageBuffer));
  22. Serial.println("Sending message to MQTT topic..");
  23. Serial.println(JSONmessageBuffer);
  24. //以下代码将打包好的JSON数据包通过PubSubClient库发送到服务器
  25. if (client.publish(topic_properties_report, JSONmessageBuffer) == true) {
  26. Serial.println("Success sending message");
  27. } else {
  28. Serial.println("Error sending message");
  29. }
  30. //由于本函数是放在loop中不断循环执行,所以添加client.loop()进行保持硬件的活跃度
  31. //避免由于长时间未向服务器发送消息而被服务器踢下线
  32. client.loop();
  33. Serial.println("-------------");
  34. }
  • 以下贴出相关资源网站
​​产品文档_设备接入IoTDA_设备属性上报_华为云 描述了设备属性上报的Json消息格式
ArduinoJson 6_JSON消息代码生成助手如果你使用的是V6以上的ArduinoJson库版本,请使用这个网站生成代码
ArduinoJson 5_JSON消息代码生成助手如果你使用的是V5的ArduinoJson库版本,请使用这个网站生成代码

        注意,属性上报可以是设备主动上报消息也可以是接到命令后被动上报。两次主动上报间应有一定的时间间隔,否则会导致服务器接收的消息过多,此时我们可以设置一定时器定时上报,或者当该属性改变时再主动上报给服务器。

4.4.5接收华为云IoT平台下发命令以及命令响应

        如果你的产品模型中定义了命令以及响应的命令参数,那么华为云IoT平台侧可以向设备下发命令,设备接收到命令后必须依据特定的JSON格式向华为云IoT平台侧发送一命令响应,华为云IoT平台侧接收到该命令响应后才视为命令下发成功。

  • 想要接收平台下发的命令或者消息,需要在上文的MQTT_Init()初始化函数中添加一接收消息后触发的回调函数。
  1. //接受平台下发内容的初始化
  2. client.setCallback(callback); //可以接受任何平台下发的内容

        当接受到平台侧下发给设备的任何消息或者命令时,程序就会调用括号中的回调函数(callback),我们在此回调函数中处理接收到的消息,执行相关操作。

下面贴上我一个项目中的回调函数代码

        读者可以根据自己的需要修改其中的参数。

        注释里有对代码的详细解释,请仔细阅读注释。

  • 该函数主要分为三个部分
  1. 将接收到的命令或者消息在串口展示出来;
  2. 提取出平台发送该命令时Topic中的request_id,并将其与命令响应Topic连接起来,以便进行命令的响应;
  3. 对接收到的命令或者消息进行解析后,执行响应操作。
  1. //监听华为云IoT平台下发指令并处理
  2. void callback(char *topic, byte *payload, unsigned int length)
  3. {
  4. char *pstr = topic; //指向topic字符串,提取request_id用
  5. /*串口打印出收到的平台消息或者命令*/
  6. Serial.println();
  7. Serial.println();
  8. Serial.print("Message arrived [");
  9. Serial.print(topic); //将收到消息的topic展示出来
  10. Serial.print("] ");
  11. Serial.println();
  12. payload[length] = '\0'; //在收到的内容后面加上字符串结束符
  13. char strPayload[255] = {0};
  14. strcpy(strPayload, (const char*)payload);
  15. Serial.println((char *)payload); //打印出收到的内容
  16. Serial.println(strPayload);
  17. /*request_id解析部分*///后文有详细解释为什么要提取下发命令的request_id
  18. char arr[100]; //存放request_id
  19. int flag = 0;
  20. char *p = arr;
  21. while(*pstr) //以'='为标志,提取出request_id
  22. {
  23. if(flag) *p ++ = *pstr;
  24. if(*pstr == '=') flag = 1;
  25. pstr++;
  26. }
  27. *p = '\0';
  28. Serial.println(arr);
  29. // strcat(topic_Commands_Response, arr);
  30. // topic_Commands_Response.concat(arr);
  31. /*将命令响应topic与resquest_id结合起来*/
  32. char topicRes[200] = {0};
  33. strcat(topicRes, topic_Commands_Response);
  34. strcat(topicRes, arr);
  35. Serial.println(topicRes);
  36. /*payload解析*///这是对接收到的平台下发的消息或者命令进行解析
  37. //解析程序同样可以由ArduinoJson库官方网站的ArduinoJson助手生成
  38. const size_t capacity_Payload_Receive = JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(5) + 150;
  39. DynamicJsonBuffer jsonBuffer_Payload(capacity_Payload_Receive);
  40. JsonObject& root_Payload = jsonBuffer_Payload.parseObject(strPayload);
  41. //以下就是根据不同的命令或者消息进行不同的响应,此部分代码请自行修改
  42. if (root_Payload.success()){ //判断json解析是否成功
  43. if(!strcmp(root_Payload["command_name"], "user_order")) //如果收到的内容是“用户下单”
  44. {
  45. JsonObject& paras_Payload = root_Payload["paras"];
  46. const char* paras_address = paras_Payload["address"]; // "88—902"
  47. const char* paras_user = paras_Payload["user"]; // "wksgogogo"
  48. const char* paras_number = paras_Payload["number"]; // "3333"
  49. const char* paras_day = paras_Payload["day"]; // "2022-07-21"
  50. const char* paras_time = paras_Payload["time"]; // "12:01"
  51. Serial.println("__________JSON Received Parse__________");
  52. Serial.println(paras_address);
  53. Serial.println(paras_user);
  54. Serial.println(paras_number);
  55. Serial.println(paras_day);
  56. Serial.println(paras_time);
  57. Info_UserOrder_Structure OrderInfo;
  58. strcpy(OrderInfo.userName, paras_user);
  59. strcpy(OrderInfo.address, paras_address);
  60. strcpy(OrderInfo.orderNum, paras_number);
  61. strcpy(OrderInfo.day, paras_day);
  62. strcpy(OrderInfo.time, paras_time);
  63. //响应函数会在下文贴出
  64. Command_Response(topicRes, "user_order", SUCCESS);
  65. OrderInfo_Save(OrderInfo); //订单信息存储
  66. }
  67. if(!strcmp(root_Payload["command_name"], "open")) //如果收到的内容是“开锁”
  68. {
  69. const char* paras_user = root_Payload["paras"]["user"];
  70. Serial.println("__________JSON Received Parse__________");
  71. Serial.println(paras_user);
  72. EOF_ELock_Unlock(paras_user);
  73. Command_Response(topicRes, "open", SUCCESS);
  74. }
  75. }

命令响应代码

        读者可以根据自己的需要修改其中的参数;

        注释里有对代码的详细解释,请仔细阅读注释;

        此部分代码的原理与属性上报的代码原理基本相同。

  1. void Command_Response(char *topic, char *responseName, uint8_t response_Result)
  2. {
  3. /*发送命令响应部分*/
  4. /*构建JSON内容*/
  5. const size_t capacity = JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(3);
  6. DynamicJsonBuffer jsonBuffer(capacity);
  7. JsonObject& root = jsonBuffer.createObject();
  8. if(response_Result == SUCCESS){
  9. root["result_code"] = 0;
  10. JsonObject& paras = root.createNestedObject("paras");
  11. paras["status"] = 200;
  12. paras["msg"] = "success";
  13. }
  14. else if(response_Result == FAIL){
  15. root["result_code"] = 1;
  16. JsonObject& paras = root.createNestedObject("paras");
  17. paras["status"] = 400;
  18. paras["msg"] = "fail";
  19. }
  20. if(!strcmp(responseName, "user_order")){
  21. root["response_name"] = "user_order";
  22. }
  23. else if(!strcmp(responseName, "open")){
  24. root["response_name"] = "open";
  25. }
  26. root.printTo(Serial); //串口打印出构建好的JSON内容
  27. Serial.println();
  28. char JSONmessageBuffer[300];
  29. root.printTo(JSONmessageBuffer, sizeof(JSONmessageBuffer)); //将构建的JSON消息复制到char数组中
  30. Serial.println("Sending response to HuaWei Cloud..");
  31. Serial.println(JSONmessageBuffer);
  32. if (client.publish(topic, JSONmessageBuffer) == true) {
  33. Serial.println("Success sending response command message");
  34. } else {
  35. Serial.println("Error sending response command message");
  36. }
  37. Serial.println("-------------");
  38. }
  • 关于命令响应的一些说明:

华为云产品文档中对于命令下发与命令响应的解释

平台命令下发_设备接入 IoTDA_API参考_设备侧MQTT/MQTTS接口参考_设备命令_华为云

Topic

下行: $oc/devices/{device_id}/sys/commands/request_id={request_id}

上行:$oc/devices/{device_id}/sys/commands/response/request_id={request_id}

  • {request_id}用于唯一标识这次请求。设备侧收到下行请求的topic带该参数时,上行响应的topic需要将该参数值返回给平台。
  • 设备侧订阅带{request_id}结尾的topic时,可以使用通配#,设备侧订阅平台命令下发的topic为:$oc/devices/{device_id}/sys/commands/#

        也就是说,接收华为云IoT平台下发命令时,我们可以不关心Topic中的{request_id}而使用通配符#代替,但是设备接收到命令后必须响应此命令,此时{request_id}不可省略。所以在上文中的callback函数中我们才需要解析出平台下发命令的Topic中的{request_id},并将其接到定义的命令响应Topic中,通过此Topic进行命令的响应。

        下面是宏定义的命令响应Topic,在其后面接上命令下发的{request_id}后才可以用此Topic向平台响应命令。

char* topic_Commands_Response = "$oc/devices/{device_id}/sys/commands/response/request_id=";

        命令响应Topic中的{request_id}与要响应的命令Topic中的{request_id}一致。

        以上便是ESP32与华为云IoT平台的连接过程,希望对你有所帮助。

        笔者道行不深,但努力学习,还请多多指教。

        需要相关代码,请留言评论,笔者会在后续更新。

        如果教程中还有其他不懂的,请评论或者私信我,笔者会尽可能回答。

原创不易,点个赞或者点个关注激励笔者分享更多优质原创内容吧!

开源万岁!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/204265
推荐阅读
相关标签
  

闽ICP备14008679号