订阅
纠错
加入自媒体

一文教你如何利用鸿蒙OS实现智慧家居·LOT上云项目

2021-06-24 13:39
一口Linux
关注

三、软件编写

我们使用的鸿蒙OS源码,已经包含了MQTT等常用的模块,在示例工程中可以方便的查找。这里先简单讲一下目录结构,熟悉一下整个鸿蒙OS在源码框架上的细节。

这里使用的鸿蒙源码工程,由HPM包管理器获取,具体源码结构如下:

我们列一张表,看看每一个文件夹具体承担了哪些职能:

文件名称描述applicationsBearPi-HM_Nano开发板应用案例base系统的基础服务,主要使用DFX子系统、启动文件、硬件适配接口等kernel内核子系统ohos_bundles厂家提供的一些组件和服务third_party第三方组件foundation系统服务框架子系统、WAN开发headers存放main头文件src存放 main源文件utils公共基础库testXTS认证子系统vendor硬件抽象层build编译构建子系统out存放编译文件bin存放二进制文件

适合本文项目的代码示例,在applications文件夹,具体目录为:applicationsBearPiBearPi-HM_NanosampleD6_iot_cloud_oc。这里列一下目录文件结构:

文件名称描述E53_IA1.c扩展板驱动oc_mqtt_profile_package.c打包和配置MQTT数据oc_mqtt.cMQTT连接服务wifi_connet.cwifi连接服务iot_cloud_oc_sample.c业务逻辑代码

我们主要要用到的API如下,具体实现的细节,可以到源文件里面去阅读。可以分为初始化和数据上传两个部分。

1. 初始化1)设备信息

void device_info_init(char *client_id, char * username, char *password);

设置设备信息,在调用oc_mqtt_init()前要先设置设备信息

参数描述无无返回描述0成功-1获得设备信息失败-2mqtt 客户端初始化失败2)华为IoT平台 初始化

int oc_mqtt_init(void);

华为IoT平台初始化函数,需要在使用 华为IoT平台 功能前调用。

参数描述无无返回描述0成功-1获得设备信息失败-2mqtt 客户端初始化失败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设备idpayload要上传的消息返回描述0上传成功1上传失败2)设备上报属性数据

int oc_mqtt_profile_propertyreport(char *deviceid,oc_mqtt_profile_service_t *payload);

用于设备按产品模型中定义的格式将属性数据上报给平台。

参数描述deviceid设备idpayload要上传的消息返回描述0上传成功1上传失败

属性上报和消息上报的区别,请查看消息通信说明

3)网关批量上报属性数据

int oc_mqtt_profile_gwpropertyreport(char *deviceid,oc_mqtt_profile_device_t *payload);

用于批量设备上报属性数据给平台。网关设备可以用此接口同时上报多个子设备的属性数据。

参数描述deviceid设备idpayload要上传的消息返回描述0上传成功1上传失败4)属性设置的响应结果

int oc_mqtt_profile_propertysetresp(char *deviceid,oc_mqtt_profile_propertysetresp_t *payload);

参数描述deviceid设备idpayload消息返回描述0上传成功1上传失败5)属性查询响应结果

int oc_mqtt_profile_propertygetresp(char *deviceid,oc_mqtt_profile_propertygetresp_t *payload);

参数描述deviceid设备idpayload消息返回描述0上传成功1上传失败6)将命令的执行结果返回给平台

int oc_mqtt_profile_cmdresp(char *deviceid,oc_mqtt_profile_cmdresp_t *payload);平台下发命令后,需要设备及时将命令的执行结果返回给平台,如果设备没回响应,平台会认为命令执行超时。

参数描述deviceid设备idpayload要上传的消息返回描述0上传成功1上传失败3. 编写业务逻辑1)连接平台

准备好上文我们获取的连接信息(ClientId、Username、Password),一个可以上网的WIFI(账户和密码),注意不可以用5G频段。

#define CLIENT_ID "60cdaf505f880902bcaa161c_senser_0_0_2021062002"
#define USERNAME "60cdaf505f880902bcaa161c_senser"
#define PASSWORD "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   处理上报的数据。
* @details   Process the reported data.
* @param[in] report  需要上报的数据。The data to be reported.
* @return    None
**
static void deal_report_msg(report_t *report)
{
* 定义服务ID句柄
   oc_mqtt_profile_service_t    service;
   * 定义温度的上报数据句柄
   oc_mqtt_profile_kv_t         temperature;
   * 定义湿度的上报数据句柄
   oc_mqtt_profile_kv_t         humidity;
   * 定义亮度的上报数据句柄
   oc_mqtt_profile_kv_t         luminance;
   * 定义电灯的上报数据句柄
   oc_mqtt_profile_kv_t         led;
   * 定义电机的上报数据句柄
   oc_mqtt_profile_kv_t         motor;
* 初始化要上报的服务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   将命令数据发送到队列。
* @details   Send command data to the queue.
* @param[in] recv_data  接收的数据
* @param[in] recv_size  接收数据的大小
* @param[in] resp_data  接收的上报数据
* @param[in] resp_size  接收的上报数据的大小
* @return    None
**
void oc_cmd_rsp_cb(uint8_t *recv_data, size_t recv_size, uint8_t **resp_data, size_t *resp_size)
{
app_msg_t *app_msg;
int ret = 0;
app_msg = malloc(sizeof(app_msg_t));
app_msg->msg_type = en_msg_cmd;
app_msg->msg.cmd.payload = (char *)recv_data;
   printf("recv data is %.*s", 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   线程入口,读取队列数据并处理。
* @details   Thread entry, read queue data and process.
* @param[in] None
* @return    None
**
static int task_main_entry( void )
{
   app_msg_t *app_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){
               case en_msg_cmd:
                   deal_cmd_msg(&app_msg->msg.cmd);
                   break;
               case en_msg_report:
                   deal_report_msg(&app_msg->msg.report);
                   break;
               default:
                   break;
           }
           free(app_msg);
       }
   }
   return 0;
}
*
* @brief   解析命令,并给出处理的结果。
* @details   Thread entry, read queue data and process.
* @param[in] cmd 命令。
* @return    None
**
static void deal_cmd_msg(cmd_t *cmd)
{
   cJSON *obj_root;
   cJSON *obj_cmdname;
   cJSON *obj_paras;
   cJSON *obj_para;
   int cmdret = 1;
   oc_mqtt_profile_cmdresp_t  cmdresp;
   obj_root = cJSON_Parse(cmd->payload);
   if(NULL == obj_root){
       goto EXIT_JSONPARSE;
   }
   obj_cmdname = cJSON_GetObjectItem(obj_root,"command_name");
   if(NULL == obj_cmdname){
       goto EXIT_CMDOBJ;
   }
   if(0 == strcmp(cJSON_GetStringValue(obj_cmdname),"Agriculture_Control_light")){
       obj_paras = cJSON_GetObjectItem(obj_root,"paras");
       if(NULL == obj_paras){
           goto EXIT_OBJPARAS;
       }
       obj_para = cJSON_GetObjectItem(obj_paras,"light");
       if(NULL == obj_para){
           goto EXIT_OBJPARA;
       }
       ///< operate the LED here
       if(0 == strcmp(cJSON_GetStringValue(obj_para),"ON")){
           g_app_cb.led = 1;
           Light_StatusSet(ON);
           printf("Light On!");
       }
       else{
           g_app_cb.led = 0;
           Light_StatusSet(OFF);
           printf("Light Off!");
       }
       cmdret = 0;
   }
   else if(0 == strcmp(cJSON_GetStringValue(obj_cmdname),"Agriculture_Control_Motor")){
       obj_paras = cJSON_GetObjectItem(obj_root,"paras");
       if(NULL == obj_paras){
           goto EXIT_OBJPARAS;
       }
       obj_para = cJSON_GetObjectItem(obj_paras,"motor");
       if(NULL == obj_para){
           goto EXIT_OBJPARA;
       }
       ///< operate the Motor here
       if(0 == strcmp(cJSON_GetStringValue(obj_para),"ON")){
           g_app_cb.motor = 1;
           Motor_StatusSet(ON);
           printf("Motor On!");
       }
       else{
           g_app_cb.motor = 0;
           Motor_StatusSet(OFF);
           printf("Motor Off!");
       }
       cmdret = 0;
   }
EXIT_OBJPARA:
EXIT_OBJPARAS:
EXIT_CMDOBJ:
   cJSON_Delete(obj_root);
EXIT_JSONPARSE:
   ///< do the response
   cmdresp.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. 编译调试

修改 applicationssampleBearPiBearPi-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按键,通过串口助手查看日志,会打印温湿度及光照强度信息。

sdk ver:Hi3861V100R001C00SPC025 2020-09-03 18:10:00
FileSystem mount ok.
wifi init success!
00 00:00:00 0 68 D 0/HIVIEW: hilog init success.
00 00:00:00 0 68 D 0/HIVIEW: log limit init success.
00 00:00:00 0 68 I 1/SAMGR: Bootstrap core services(count:3).
00 00:00:00 0 68 I 1/SAMGR: Init service:0x4b8040 TaskPool:0xfa9a4
00 00:00:00 0 68 I 1/SAMGR: Init service:0x4b8064 TaskPool:0xfb014
00 00:00:00 0 68 I 1/SAMGR: Init service:0x4b81c8 TaskPool:0xfb1d4
00 00:00:00 0 100 I 1/SAMGR: Init service 0x4b8064

回到华为云平台,平台上的设备显示为在线状态

点击设备右侧的“查看”,进入设备详情页面,可看到上报的数据

在华为云平台设备详情页,单击“命令”,选择同步命令下发,选中创建的命令属性,单击“确定”,即可发送下发命令控制设备。

看一下现象:串口打印云端接收的数据,并执行点灯的指令。

5. 调试华为云API

点击「API检索和调试」,进入API调测界面。

目前开放有Java、python、node.js、php等,可以根据个人的需求,构建前端。这里我们先调试API,选择一个设备命令,按照图示操作。注意Body里面的参数,与我们上文产品的属性是一样的,其中paras的参数,填写要符合图片给出的规范,也就是JSON的格式。

最后点击调式,给出调试结果,我们的开发板上,灯也被点亮!

四、总结

云端的操作,要注意和终端软件编写的信息相同,一个是MQTT的连接信息不能出错,还有就是注意名称之间的大小写要相同;终端MCU软件的编写,注意分层设计,先写好各自的功能模块,最后再实现相关的业务逻辑;注意调测,利用好串口和云端MQTT信息跟踪服务;整体走下来,工作量还是蛮大的,需要注意的地方有很多,所以要特别细心。源代码后台回复鸿蒙获取,工程文件可以参考上个文章获取。

<上一页  1  2  
声明: 本文由入驻维科号的作者撰写,观点仅代表作者本人,不代表OFweek立场。如有侵权或其他问题,请联系举报。

发表评论

0条评论,0人参与

请输入评论内容...

请输入评论/评论长度6~500个字

您提交的评论过于频繁,请输入验证码继续

暂无评论

暂无评论

    人工智能 猎头职位 更多
    扫码关注公众号
    OFweek人工智能网
    获取更多精彩内容
    文章纠错
    x
    *文字标题:
    *纠错内容:
    联系邮箱:
    *验 证 码:

    粤公网安备 44030502002758号