以上,我们云服务器的配置,先到此为止,接下来就是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_t*report)
{
/**定义服务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_t*recv_data,size_trecv_size,uint8_t**resp_data,size_t*resp_size)
{
app_msg_t*app_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_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){
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_t*cmd)
{
cJSON*obj_root;
cJSON*obj_cmdname;
cJSON*obj_paras;
cJSON*obj_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;
}
///<operatetheLEDhere
if(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;
}
///<operatetheMotorhere
if(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:
///<dotheresponse
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. 编译调试
修改 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:00
FileSystemmountok.
wifiinitsuccess!
0000:00:00068D0/HIVIEW:hiloginitsuccess.
0000:00:00068D0/HIVIEW:loglimitinitsuccess.
0000:00:00068I1/SAMGR:Bootstrapcoreservices(count:3).
0000:00:00068I1/SAMGR:Initservice:0x4b8040TaskPool:0xfa9a4
0000:00:00068I1/SAMGR:Initservice:0x4b8064TaskPool:0xfb014
0000:00:00068I1/SAMGR:Initservice:0x4b81c8TaskPool:0xfb1d4
0000: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:SCANFINISH
callbackfunctionforwifiscan:1,24
WaitSacnResult:waitsuccess[1]s
********************
no:001,ssid:养只狗叫瑞邦,rssi:-53
no:002,ssid:电信302,rssi:-63
no:003,ssid:412,rssi:-64
no:004,ssid:DIRECT-IXLAPTOP-O3K3OKASmsUK,rssi:-69
...
********************
Select:2wireless,Waiting...
NOTICE:CONNECTED
SENSOR:lum:67.50temp:33.17hum:68.33
WaitConnectResult:waitsuccess[1]s
WiFiconnectsucceed!
begaintodhcp
<--DHCPstate:Inprogress-->
<--DHCPstate:Inprogress-->
<--DHCPstate:OK-->
server:
server_id:192.168.1.1
mask:255.255.255.0,1
gw:192.168.1.1
T0:7200
T1:3600
T2:6300
clients<1>:
mac_idxmacaddrstateleasetriesrto
0e81131641696192.168.1.12310013
SENSOR:lum:79.17temp:32.77hum:60.45
SENSOR:lum:38.33temp:32.51hum:52.88
SENSOR:lum:42.50temp:32.30hum:50.59
SENSOR:lum:42.50temp:32.11hum:49.73
SENSOR:lum:40.00temp:31.91hum:49.74
SENSOR:lum:41.67temp:31.75hum:49.96
回到华为云平台,平台上的设备显示为在线状态
点击设备右侧的“查看”,进入设备详情页面,可看到上报的数据
在华为云平台设备详情页,单击“命令”,选择同步命令下发,选中创建的命令属性,单击“确定”,即可发送下发命令控制设备。