1、准备工作
云服务器的配置,不算复杂,主要是前端的处理和显示,可以先在服务器调通,再根据云服务提供的API,进行访问。
硬件环境使用小熊派·鸿蒙季开发板和E53_IA1扩展板(有温湿度光强传感器和电机)。
具体如何创建工程,可以参考我的上一篇文章,这里采用Windows环境下的开发方式。无论是基于HPM还是Docker环境获取鸿蒙源码创建工程,都很简单。

这个Demo,我们将会用到鸿蒙OS的内核子系统和驱动子系统。内核子系统主要使用线程相关的API(基于CMSIS-2.0)和网络服务相关的API(socket);驱动子系统主要调用底层的GPIO和硬件I2C,控制外部设备。
在内核子系统和驱动子系统上,我们还需要一个组件(软件包),物联网通信协议MQTT,利用它进行上云服务。
列一下主要的资源和工具:
硬件:
小熊派 · 鸿蒙季开发板E53_IA1扩展板源码:
Hi3861开发板的源码,来源Hb,适用于windows环境IDE
vscode(IDE平台)DevEco Device Tool (IDE组件,可选)RaiDrive本地环境:
windows10 64位ubuntu18.04云环境:
HUAWEI-LoTCloud(云服务器平台)CloudIDE(可选,用于在线调试API接口)下面,跟着我具体的操作,一步一步实现整个方案,内容比较多,务必提前装好环境,可以先看看前面的文章,把环境搭建起来。
2. 云端操作先讲云服务器这里。为了方便验证,我们首选华为云服务器(腾讯云、阿里云也可,原理大同小异)。
操作流程大致如下:
设备接入华为云平台之前,需要在平台注册用,已注册过的可忽略这一步。华为云地址:https://www.huaweicloud.com/
登陆以后,在华为云首页单击控制台,进入产品控制终端,这里包含了各种云服务的产品。
选择云服务器的地点为华为-北京四。
点击左侧的 服务器,找到物联网,选择设备接入IoTDA 并立即使用。或者在搜索输入 设备接入IoTDA跳转过去。下次选择这个服务时,直接点击搜索栏下的最近访问的服务,就能快速进入相应的服务当中,非常方便。
点击产品,选择创建产品,填写产品信息。「所属资源空间」选择默认,「产品名称」这里填写一个Smart_House(根据自己喜好写一个),「协议类型」选择MQTT就好,「数据格式」为JSON,「厂商名称」填写一口Linux,「设备类型」填写senser。点击确定,完成产品的创建。
创建完毕,弹出产品创建成功的窗口消息。
点击产品列表的「查看」,进行设备的相关操作。
定义一个服务模型,「服务ID」随便起名字,这里填入Agriculture,「服务类型」填入senser。点击确定,完成服务的添加。
接下来为服务设置属性和命令,这里规定了数据通信的基本格式。
点击「添加属性」,以温度为例,「属性名称」填写Temperature,「属性描述」填写温度,「数据类型」为整型,「访问权限」为可读,剩下的默认即可。其中「属性名称」的内容,要与后面我们在MCU中发送的信息保持一致,这里先提一句。
与温度类似,我们依次填写如下内容,不同的是灯和电机,两个的「数据类型」是字符串,「长度」为3。下图列举了灯的属性和其他的设备属性总览。
接着添加服务命令,点击「添加命令」,依次输入「命令名称」,再点击「新增输入参数」。
新增输入参数和服务属性差不多,这里是字符串的数据类型,输入枚举值,用英文逗号做分割。
我们来看一下所有的属性和命令,差不多就这样:
我们往下进行,点击「设备」, 选择「注册设备」填写设备属性, 「所属资源空间」选择默认账户的即可, 「所属产品」选择上面自己创建的产品, 「设备标识码」填写senser, 「设备名称」填写house,其他保持默认, 点击确定完成创建。
设备创建成功以后,有两个重要信息需要保存,分别是设备ID和设备密钥。
设备ID: 60cdaf505f880902bcaa161c_senser设备密钥: 4a423f69b41806de0d8ed77e145534e7
接着我们利用获取的密钥,生成直连MQTT所需的ClentID,通过这个链接跳转:https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/ https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
以上,我们云服务器的配置,先到此为止,接下来就是MCU终端上的软件编写。在我们完成软件编写以后,在进行两边的联调测试。
三、软件编写我们使用的鸿蒙OS源码,已经包含了MQTT等常用的模块,在示例工程中可以方便的查找。这里先简单讲一下目录结构,熟悉一下整个鸿蒙OS在源码框架上的细节。
这里使用的鸿蒙源码工程,由HPM包管理器获取,具体源码结构如下:
我们列一张表,看看每一个文件夹具体承担了哪些职能:
文件名称 描述 applications BearPi-HM_Nano开发板应用案例 base 系统的基础服务,主要使用DFX子系统、启动文件、硬件适配接口等 kernel 内核子系统 ohos_bundles 厂家提供的一些组件和服务 third_party 第三方组件 foundation 系统服务框架子系统、WAN开发 headers 存放main头文件 src 存放 main源文件 utils 公共基础库 test XTS认证子系统 vendor 硬件抽象层 build 编译构建子系统 out 存放编译文件 bin 存放二进制文件
适合本文项目的代码示例,在applications文件夹,具体目录为:applications\BearPi\BearPi-HM_Nano\sample\D6_iot_cloud_oc。这里列一下目录文件结构:
文件名称 描述 E53_IA1.c 扩展板驱动 oc_mqtt_profile_package.c 打包和配置MQTT数据 oc_mqtt.c MQTT连接服务 wifi_connet.c wifi连接服务 iot_cloud_oc_sample.c 业务逻辑代码
我们主要要用到的API如下,具体实现的细节,可以到源文件里面去阅读。可以分为初始化和数据上传两个部分。
1. 初始化1)设备信息void device_info_init(char client_id, char username, char password);
设置设备信息,在调用oc_mqtt_init()前要先设置设备信息
参数 描述 无 无 返回 描述 0 成功 -1 获得设备信息失败 -2 mqtt 客户端初始化失败
2)华为IoT平台 初始化int oc_mqtt_init(void);
华为IoT平台初始化函数,需要在使用 华为IoT平台 功能前调用。
参数 描述 无 无 返回 描述 0 成功 -1 获得设备信息失败 -2 mqtt 客户端初始化失败
3)设置命令响应函数void oc_set_cmd_rsp_cb(void(cmd_rsp_cb)(uint8_t recv_data, size_t recv_size, uint8_t resp_data, size_t resp_size));
设置命令响应回调函数。
参数 描述 recv_data 接收到的数据 recv_size 数据的长度 resp_data 响应数据 resp_size 响应数据的长度 返回 描述 无 无
2. 数据上传1)设备消息上报int oc_mqtt_profile_msgup(char deviceid,oc_mqtt_profile_msgup_t payload);
是指设备无法按照产品模型中定义的属性格式进行数据上报时,可调用此接口将设备的自定义数据上报给平台,平台将设备上报的消息转发给应用服务器或华为云其他云服务上进行存储和处理。
参数 描述 deviceid 设备id payload 要上传的消息 返回 描述 0 上传成功 1 上传失败
2)设备上报属性数据int oc_mqtt_profile_propertyreport(char deviceid,oc_mqtt_profile_service_t payload);
用于设备按产品模型中定义的格式将属性数据上报给平台。
参数 描述 deviceid 设备id payload 要上传的消息 返回 描述 0 上传成功 1 上传失败
属性上报和消息上报的区别,请查看消息通信说明
3)网关批量上报属性数据int oc_mqtt_profile_gwpropertyreport(char deviceid,oc_mqtt_profile_device_t payload);
用于批量设备上报属性数据给平台。网关设备可以用此接口同时上报多个子设备的属性数据。
参数 描述 deviceid 设备id payload 要上传的消息 返回 描述 0 上传成功 1 上传失败
4)属性设置的响应结果int oc_mqtt_profile_propertysetresp(char deviceid,oc_mqtt_profile_propertysetresp_t payload);
参数 描述 deviceid 设备id payload 消息 返回 描述 0 上传成功 1 上传失败
5)属性查询响应结果int oc_mqtt_profile_propertygetresp(char deviceid,oc_mqtt_profile_propertygetresp_t payload);
参数 描述 deviceid 设备id payload 消息 返回 描述 0 上传成功 1 上传失败
6)将命令的执行结果返回给平台int oc_mqtt_profile_cmdresp(char deviceid,oc_mqtt_profile_cmdresp_t payload); 平台下发命令后,需要设备及时将命令的执行结果返回给平台,如果设备没回响应,平台会认为命令执行超时。
参数 描述 deviceid 设备id payload 要上传的消息 返回 描述 0 上传成功 1 上传失败
3. 编写业务逻辑1)连接平台准备好上文我们获取的连接信息(ClientId、Username、Password),一个可以上网的WIFI(账户和密码),注意不可以用5G频段。
#defineCLIENT_ID"60cdaf505f880902bcaa161c_senser_0_0_2021062002"#defineUSERNAME"60cdaf505f880902bcaa161c_senser"#definePASSWORD"e7f839333a8d3618a975e2626df1462f67202f3f4103080fe8d6f05df0fa7ce3"WifiConnect("TP-LINK_65A8","0987654321");device_info_init(CLIENT_ID,USERNAME,PASSWORD);oc_mqtt_init();oc_set_cmd_rsp_cb(oc_cmd_rsp_cb);
2)推送数据当需要上传数据时,需要先拼装数据,然后通过oc_mqtt_profile_propertyreport上报数据。代码示例如下:
/@brief处理上报的数据。@detailsProcessthereporteddata.@param[in]report需要上报的数据。Thedatatobereported.@returnNone/staticvoiddeal_report_msg(report_treport){/定义服务ID句柄/oc_mqtt_profile_service_tservice;/定义温度的上报数据句柄/oc_mqtt_profile_kv_ttemperature;/定义湿度的上报数据句柄/oc_mqtt_profile_kv_thumidity;/定义亮度的上报数据句柄/oc_mqtt_profile_kv_tluminance;/定义电灯的上报数据句柄/oc_mqtt_profile_kv_tled;/定义电机的上报数据句柄/oc_mqtt_profile_kv_tmotor;/初始化要上报的服务ID数据/service.event_time=NULL;service.service_id="Agriculture";service.service_property=&temperature;service.nxt=NULL;/初始化要上报的温度数据/temperature.key="Temperature";temperature.value=&report->temp;temperature.type=EN_OC_MQTT_PROFILE_VALUE_INT;temperature.nxt=&humidity;/初始化要上报的湿度数据/humidity.key="Humidity";humidity.value=&report->hum;humidity.type=EN_OC_MQTT_PROFILE_VALUE_INT;humidity.nxt=&luminance;/初始化要上报的亮度数据/luminance.key="Luminance";luminance.value=&report->lum;luminance.type=EN_OC_MQTT_PROFILE_VALUE_INT;luminance.nxt=&led;/初始化要上报的电灯数据/led.key="LightStatus";led.value=g_app_cb.led?"ON":"OFF";led.type=EN_OC_MQTT_PROFILE_VALUE_STRING;led.nxt=&motor;/初始化要上报的电机数据/motor.key="MotorStatus";motor.value=g_app_cb.motor?"ON":"OFF";motor.type=EN_OC_MQTT_PROFILE_VALUE_STRING;motor.nxt=NULL;/将属性数据上报给平台/oc_mqtt_profile_propertyreport(USERNAME,&service);return;}
3)命令接收华为IoT平台支持下发命令,命令是用户自定义的。接收到命令后会将命令数据发送到队列中,task_main_entry函数中读取队列数据并调用deal_cmd_msg函数进行处理,代码示例如下:
/@brief将命令数据发送到队列。@detailsSendcommanddatatothequeue.@param[in]recv_data接收的数据@param[in]recv_size接收数据的大小@param[in]resp_data接收的上报数据@param[in]resp_size接收的上报数据的大小@returnNone/voidoc_cmd_rsp_cb(uint8_trecv_data,size_trecv_size,uint8_tresp_data,size_tresp_size){app_msg_tapp_msg;intret=0;app_msg=malloc(sizeof(app_msg_t));app_msg->msg_type=en_msg_cmd;app_msg->msg.cmd.payload=(char)recv_data;printf("recvdatais%.s\n",recv_size,recv_data);/送入队列/ret=osMessageQueuePut(mid_MsgQueue,&app_msg,0U,0U);if(ret!=0){free(recv_data);}resp_data=NULL;resp_size=0;}/@brief线程入口,读取队列数据并处理。@detailsThreadentry,readqueuedataandprocess.@param[in]None@returnNone/staticinttask_main_entry(void){app_msg_tapp_msg;/连接WIFI/WifiConnect("TP-LINK_65A8","0987654321");/注册设备的连接信息/device_info_init(CLIENT_ID,USERNAME,PASSWORD);/初始化MQTT/oc_mqtt_init();oc_set_cmd_rsp_cb(oc_cmd_rsp_cb);while(1){app_msg=NULL;(void)osMessageQueueGet(mid_MsgQueue,(void)&app_msg,NULL,0U);if(NULL!=app_msg){switch(app_msg->msg_type){caseen_msg_cmd:deal_cmd_msg(&app_msg->msg.cmd);break;caseen_msg_report:deal_report_msg(&app_msg->msg.report);break;default:break;}free(app_msg);}}return0;}/@brief解析命令,并给出处理的结果。@detailsThreadentry,readqueuedataandprocess.@param[in]cmd命令。@returnNone/staticvoiddeal_cmd_msg(cmd_tcmd){cJSONobj_root;cJSONobj_cmdname;cJSONobj_paras;cJSONobj_para;intcmdret=1;oc_mqtt_profile_cmdresp_tcmdresp;obj_root=cJSON_Parse(cmd->payload);if(NULL==obj_root){gotoEXIT_JSONPARSE;}obj_cmdname=cJSON_GetObjectItem(obj_root,"command_name");if(NULL==obj_cmdname){gotoEXIT_CMDOBJ;}if(0==strcmp(cJSON_GetStringValue(obj_cmdname),"Agriculture_Control_light")){obj_paras=cJSON_GetObjectItem(obj_root,"paras");if(NULL==obj_paras){gotoEXIT_OBJPARAS;}obj_para=cJSON_GetObjectItem(obj_paras,"light");if(NULL==obj_para){gotoEXIT_OBJPARA;}///<operatetheLEDhereif(0==strcmp(cJSON_GetStringValue(obj_para),"ON")){g_app_cb.led=1;Light_StatusSet(ON);printf("LightOn!");}else{g_app_cb.led=0;Light_StatusSet(OFF);printf("LightOff!");}cmdret=0;}elseif(0==strcmp(cJSON_GetStringValue(obj_cmdname),"Agriculture_Control_Motor")){obj_paras=cJSON_GetObjectItem(obj_root,"paras");if(NULL==obj_paras){gotoEXIT_OBJPARAS;}obj_para=cJSON_GetObjectItem(obj_paras,"motor");if(NULL==obj_para){gotoEXIT_OBJPARA;}///<operatetheMotorhereif(0==strcmp(cJSON_GetStringValue(obj_para),"ON")){g_app_cb.motor=1;Motor_StatusSet(ON);printf("MotorOn!");}else{g_app_cb.motor=0;Motor_StatusSet(OFF);printf("MotorOff!");}cmdret=0;}EXIT_OBJPARA:EXIT_OBJPARAS:EXIT_CMDOBJ:cJSON_Delete(obj_root);EXIT_JSONPARSE:///<dotheresponsecmdresp.paras=NULL;cmdresp.request_id=cmd->request_id;cmdresp.ret_code=cmdret;cmdresp.ret_name=NULL;(void)oc_mqtt_profile_cmdresp(NULL,&cmdresp);return;}
4. 编译调试修改 applications\sample\BearPi\BearPi-HM_Nano路径下 BUILD.gn 文件,指定 oc_mqtt 参与编译。
#"D1_iot_wifi_sta:wifi_sta",#"D2_iot_wifi_sta_connect:wifi_sta_connect",#"D3_iot_udp_client:udp_client",#"D4_iot_tcp_server:tcp_server",#"D5_iot_mqtt:iot_mqtt","D6_iot_cloud_oc:oc_mqtt",#"D7_iot_cloud_onenet:onenet_mqtt",
示例代码编译烧录代码后,按下开发板的RESET按键,通过串口助手查看日志,会打印温湿度及光照强度信息。
sdkver:Hi3861V100R001C00SPC0252020-09-0318:10:00FileSystemmountok.wifiinitsuccess!0000:00:00068D0/HIVIEW:hiloginitsuccess.0000:00:00068D0/HIVIEW:loglimitinitsuccess.0000:00:00068I1/SAMGR:Bootstrapcoreservices(count:3).0000:00:00068I1/SAMGR:Initservice:0x4b8040TaskPool:0xfa9a40000:00:00068I1/SAMGR:Initservice:0x4b8064TaskPool:0xfb0140000:00:00068I1/SAMGR:Initservice:0x4b81c8TaskPool:0xfb1d40000:00:000100I1/SAMGR:Initservice0x4b8064<time:0ms>success!0000:00:0000I1/SAMGR:Initservice0x4b8040<time:0ms>success!0000:00:000200D0/HIVIEW:hiviewinitsuccess.0000:00:000200I1/SAMGR:Initservice0x4b81c8<time:0ms>success!0000:00:000200I1/SAMGR:Initializedallcoresystemservices!0000:00:0000I1/SAMGR:Bootstrapsystemandapplicationservices(count:0).0000:00:0000I1/SAMGR:Initializedallsystemandapplicationservices!0000:00:0000I1/SAMGR:Bootstrapdynamicregisteredservices(count:0).SENSOR:lum:107.50temp:33.34hum:63.95<--SystemInit--><--WifiInit-->registerwifieventsucceed!callbackfunctionforwifiscan:0,0+NOTICE:SCANFINISHcallbackfunctionforwifiscan:1,24WaitSacnResult:waitsuccess[1]sno:001,ssid:养只狗叫瑞邦,rssi:-53no:002,ssid:电信302,rssi:-63no:003,ssid:412,rssi:-64no:004,ssid:DIRECT-IXLAPTOP-O3K3OKASmsUK,rssi:-69...Select:2wireless,Waiting...+NOTICE:CONNECTEDSENSOR:lum:67.50temp:33.17hum:68.33WaitConnectResult:waitsuccess[1]sWiFiconnectsucceed!begaintodhcp<--DHCPstate:Inprogress--><--DHCPstate:Inprogress--><--DHCPstate:OK-->server:server_id:192.168.1.1mask:255.255.255.0,1gw:192.168.1.1T0:7200T1:3600T2:6300clients<1>:mac_idxmacaddrstateleasetriesrto0e81131641696192.168.1.12310013SENSOR:lum:79.17temp:32.77hum:60.45SENSOR:lum:38.33temp:32.51hum:52.88SENSOR:lum:42.50temp:32.30hum:50.59SENSOR:lum:42.50temp:32.11hum:49.73SENSOR:lum:40.00temp:31.91hum:49.74SENSOR:lum:41.67temp:31.75hum:49.96
回到华为云平台,平台上的设备显示为在线状态
点击设备右侧的“查看”,进入设备详情页面,可看到上报的数据
在华为云平台设备详情页,单击“命令”,选择同步命令下发,选中创建的命令属性,单击“确定”,即可发送下发命令控制设备。
看一下现象:串口打印云端接收的数据,并执行点灯的指令。
5. 调试华为云API
点击「API检索和调试」,进入API调测界面。
目前开放有Java、python、node.js、php等,可以根据个人的需求,构建前端。这里我们先调试API,选择一个设备命令,按照图示操作。 注意Body里面的参数,与我们上文产品的属性是一样的,其中paras的参数,填写要符合图片给出的规范,也就是JSON的格式。
最后点击调试,给出调试结果,我们的开发板上,灯也被点亮!
四、总结云端的操作,要注意和终端软件编写的信息相同,一个是MQTT的连接信息不能出错,还有就是注意名称之间的大小写要相同;终端MCU软件的编写,注意分层设计,先写好各自的功能模块,最后再实现相关的业务逻辑;注意调测,利用好串口和云端MQTT信息跟踪服务;整体走下来,工作量还是蛮大的,需要注意的地方有很多,所以要特别细心。源代码后台回复鸿蒙获取,工程文件可以参考上个文章获取。