目录
原创不易,点个赞或者点个关注激励笔者分享更多优质原创内容吧!
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头文件的包含
这四个头文件都是必须的
#include <wire.h>
#include <wifi.h>
#include <pubsubclient.h>
#include <arduinojson.h>
4.4.2接入参数以及esp32wifi的配置
将其中的参数按照实际情况修改
/*mqtt连接配置*/
/*-----------------------------------------------------*/
const char* ssid = "esp32连接的wifi名称";
const char* password = "wifi密码";
const char* mqttserver = "华为云mqtt接入地址";
const int mqttport = 1883;
//以下3个参数可以由hmacsha256算法生成,为硬件通过mqtt协议接入华为云iot平台的鉴权依据
const char* clientid = "";
const char* mqttuser = "";
const char* mqttpassword = "";
wificlient espclient; //esp32wifi模型定义
pubsubclient client(espclient);
const char* topic_properties_report = "属性上报topic";
//接收到命令后上发的响应topic
char* topic_commands_response = "$oc/devices/设备id/sys/commands/response/request_id=";
/*******************************************************/
ps:
接入地址
华为云mqtt接入地址以及端口号1883可以在华为云iotda的 总览-> 平台接入地址 查看
鉴权信息
clientid、mqttuser、mqttpassword是设备接入华为云iot平台时要验证的鉴权信息,可以通过hmacsha256算法实时在程序中生成,也可以通过产品文档中提供的参数生成工具直接生成(参数生成工具生成的不校验时间戳)
鉴权信息生成工具的使用
使用刚刚保存的设备id 和 密钥,填入,生成鉴权信息
topic
至于属性上报和命令响应等topic定义,在华为云iotda-> 产品-> 选择要查看的产品-> topic管理处查看
依据要使用的功能在程序中定义不同的topic,注意{device_id}应该整个替换为设备id,大括号也要替换
4.4.3wifi配置与mqtt连接初始化
代码
/*
* 作用: esp32的wifi初始化以及与mqtt服务器的连接
* 参数: 无
* 返回值:无
*/
void mqtt_init()
{
//wifi网络连接部分
wifi.begin(ssid, password); //开启esp32的wifi
while (wifi.status() != wl_connected) { //esp尝试连接到wifi网络
delay(3000);
serial.println("connecting to wifi...");
}
serial.println("connected to the wifi network");
//mqtt服务器连接部分
client.setserver(mqttserver, mqttport); //设置连接到mqtt服务器的参数
client.setkeepalive (60); //设置心跳时间
while (!client.connected()) { //尝试与mqtt服务器建立连接
serial.println("connecting to mqtt...");
if (client.connect(clientid, mqttuser, mqttpassword )) {
serial.println("connected");
} else {
serial.print("failed with state ");
serial.print(client.state());
delay(6000);
}
}
//接受平台下发内容的初始化
client.setcallback(callback); //可以接受任何平台下发的内容
}
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";
属性上报的相关代码
- 以下是我的一个物联网项目的属性上报代码,读者可以根据自己的需要修改其中的参数
注释里有对代码的详细解释,请仔细阅读注释
/*
* 作用: 垃圾桶容量上报到mqtt服务器任务
* 参数: (int)垃圾桶容量,1代表垃圾桶满了,0代表垃圾桶未满
* 返回值:无
* 命名说明:capacity:容量
*/
void task_capacity_report(int capacity)
{
//以下部分代码调用了arduinojson库将属性上报消息打包为json格式
//此部分代码可以通过arduinojson库的官方网站直接生成
staticjsonbuffer<300> jsonbuffer; //定义静态的json缓冲区用于存储json消息
jsonobject& root = jsonbuffer.createobject();
jsonarray& services = root.createnestedarray("services");
jsonobject& service_1 = services.createnestedobject();
jsonobject& properties_1_1 = service_1.createnestedobject("properties");
service_1["service_id"] = "ash_bin_service";
properties_1_1["ash_bin_capacity"] = capacity;
// root.prettyprintto(serial);//调试用,将json打印到串口
//以下将生成好的json格式消息格式化输出到字符数组中,便于下面通过pubsubclient库发送到服务器
char jsonmessagebuffer[100];
root.printto(jsonmessagebuffer, sizeof(jsonmessagebuffer));
serial.println("sending message to mqtt topic..");
serial.println(jsonmessagebuffer);
//以下代码将打包好的json数据包通过pubsubclient库发送到服务器
if (client.publish(topic_properties_report, jsonmessagebuffer) == true) {
serial.println("success sending message");
} else {
serial.println("error sending message");
}
//由于本函数是放在loop中不断循环执行,所以添加client.loop()进行保持硬件的活跃度
//避免由于长时间未向服务器发送消息而被服务器踢下线
client.loop();
serial.println("-------------");
}
- 以下贴出相关资源网站
产品文档_设备接入iotda_设备属性上报_华为云 | 描述了设备属性上报的json消息格式 |
arduinojson 6_json消息代码生成助手 | 如果你使用的是v6以上的arduinojson库版本,请使用这个网站生成代码 |
arduinojson 5_json消息代码生成助手 | 如果你使用的是v5的arduinojson库版本,请使用这个网站生成代码 |
注意,属性上报可以是设备主动上报消息也可以是接到命令后被动上报。两次主动上报间应有一定的时间间隔,否则会导致服务器接收的消息过多,此时我们可以设置一定时器定时上报,或者当该属性改变时再主动上报给服务器。
4.4.5接收华为云iot平台下发命令以及命令响应
如果你的产品模型中定义了命令以及响应的命令参数,那么华为云iot平台侧可以向设备下发命令,设备接收到命令后必须依据特定的json格式向华为云iot平台侧发送一命令响应,华为云iot平台侧接收到该命令响应后才视为命令下发成功。
- 想要接收平台下发的命令或者消息,需要在上文的mqtt_init()初始化函数中添加一接收消息后触发的回调函数。
//接受平台下发内容的初始化
client.setcallback(callback); //可以接受任何平台下发的内容
当接受到平台侧下发给设备的任何消息或者命令时,程序就会调用括号中的回调函数(callback),我们在此回调函数中处理接收到的消息,执行相关操作。
下面贴上我一个项目中的回调函数代码
读者可以根据自己的需要修改其中的参数。
注释里有对代码的详细解释,请仔细阅读注释。
- 该函数主要分为三个部分
- 将接收到的命令或者消息在串口展示出来;
- 提取出平台发送该命令时topic中的request_id,并将其与命令响应topic连接起来,以便进行命令的响应;
- 对接收到的命令或者消息进行解析后,执行响应操作。
//监听华为云iot平台下发指令并处理
void callback(char *topic, byte *payload, unsigned int length)
{
char *pstr = topic; //指向topic字符串,提取request_id用
/*串口打印出收到的平台消息或者命令*/
serial.println();
serial.println();
serial.print("message arrived [");
serial.print(topic); //将收到消息的topic展示出来
serial.print("] ");
serial.println();
payload[length] = '\0'; //在收到的内容后面加上字符串结束符
char strpayload[255] = {0};
strcpy(strpayload, (const char*)payload);
serial.println((char *)payload); //打印出收到的内容
serial.println(strpayload);
/*request_id解析部分*///后文有详细解释为什么要提取下发命令的request_id
char arr[100]; //存放request_id
int flag = 0;
char *p = arr;
while(*pstr) //以'='为标志,提取出request_id
{
if(flag) *p ++ = *pstr;
if(*pstr == '=') flag = 1;
pstr++;
}
*p = '\0';
serial.println(arr);
// strcat(topic_commands_response, arr);
// topic_commands_response.concat(arr);
/*将命令响应topic与resquest_id结合起来*/
char topicres[200] = {0};
strcat(topicres, topic_commands_response);
strcat(topicres, arr);
serial.println(topicres);
/*payload解析*///这是对接收到的平台下发的消息或者命令进行解析
//解析程序同样可以由arduinojson库官方网站的arduinojson助手生成
const size_t capacity_payload_receive = json_object_size(3) + json_object_size(5) + 150;
dynamicjsonbuffer jsonbuffer_payload(capacity_payload_receive);
jsonobject& root_payload = jsonbuffer_payload.parseobject(strpayload);
//以下就是根据不同的命令或者消息进行不同的响应,此部分代码请自行修改
if (root_payload.success()){ //判断json解析是否成功
if(!strcmp(root_payload["command_name"], "user_order")) //如果收到的内容是“用户下单”
{
jsonobject& paras_payload = root_payload["paras"];
const char* paras_address = paras_payload["address"]; // "88—902"
const char* paras_user = paras_payload["user"]; // "wksgogogo"
const char* paras_number = paras_payload["number"]; // "3333"
const char* paras_day = paras_payload["day"]; // "2022-07-21"
const char* paras_time = paras_payload["time"]; // "12:01"
serial.println("__________json received parse__________");
serial.println(paras_address);
serial.println(paras_user);
serial.println(paras_number);
serial.println(paras_day);
serial.println(paras_time);
info_userorder_structure orderinfo;
strcpy(orderinfo.username, paras_user);
strcpy(orderinfo.address, paras_address);
strcpy(orderinfo.ordernum, paras_number);
strcpy(orderinfo.day, paras_day);
strcpy(orderinfo.time, paras_time);
//响应函数会在下文贴出
command_response(topicres, "user_order", success);
orderinfo_save(orderinfo); //订单信息存储
}
if(!strcmp(root_payload["command_name"], "open")) //如果收到的内容是“开锁”
{
const char* paras_user = root_payload["paras"]["user"];
serial.println("__________json received parse__________");
serial.println(paras_user);
eof_elock_unlock(paras_user);
command_response(topicres, "open", success);
}
}
命令响应代码
读者可以根据自己的需要修改其中的参数;
注释里有对代码的详细解释,请仔细阅读注释;
此部分代码的原理与属性上报的代码原理基本相同。
void command_response(char *topic, char *responsename, uint8_t response_result)
{
/*发送命令响应部分*/
/*构建json内容*/
const size_t capacity = json_object_size(2) + json_object_size(3);
dynamicjsonbuffer jsonbuffer(capacity);
jsonobject& root = jsonbuffer.createobject();
if(response_result == success){
root["result_code"] = 0;
jsonobject& paras = root.createnestedobject("paras");
paras["status"] = 200;
paras["msg"] = "success";
}
else if(response_result == fail){
root["result_code"] = 1;
jsonobject& paras = root.createnestedobject("paras");
paras["status"] = 400;
paras["msg"] = "fail";
}
if(!strcmp(responsename, "user_order")){
root["response_name"] = "user_order";
}
else if(!strcmp(responsename, "open")){
root["response_name"] = "open";
}
root.printto(serial); //串口打印出构建好的json内容
serial.println();
char jsonmessagebuffer[300];
root.printto(jsonmessagebuffer, sizeof(jsonmessagebuffer)); //将构建的json消息复制到char数组中
serial.println("sending response to huawei cloud..");
serial.println(jsonmessagebuffer);
if (client.publish(topic, jsonmessagebuffer) == true) {
serial.println("success sending response command message");
} else {
serial.println("error sending response command message");
}
serial.println("-------------");
}
- 关于命令响应的一些说明:
华为云产品文档中对于命令下发与命令响应的解释
平台命令下发_设备接入 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}
也就是说,接收华为云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平台的连接过程,希望对你有所帮助。
笔者道行不深,但努力学习,还请多多指教。
需要相关代码,请留言评论,笔者会在后续更新。
如果教程中还有其他不懂的,请评论或者私信我,笔者会尽可能回答。
发表评论