一、平台简介
设备接入服务(IoTDA) 是华为云的物联网平台,提供海量设备连接上云、设备和云端双向消息通信、批量设备管理、远程控制和监控、OTA升级、设备联动规则等能力,并可将设备数据灵活流转到华为云其他服务。
官网主页:https://www.huaweicloud.com/product/iothub.html
帮助文档:https://support.huaweicloud.com/iothub/index.html
平台架构:
二、Demo体验与SDK下载
2.1 创建产品
单击左侧导航栏“产品”,单击页面右上角的“创建产品”。根据页面提示填写参数,然后单击“确定”,完成产品的创建。
2.2 创建产品模型
产品模型用于描述设备具备的能力和特性。开发者通过定义产品模型,在物联网平台构建一款设备的抽象模型,使平台理解该款设备支持的服务、属性、命令等信息,如颜色、开关等。当定义完一款产品模型后,再进行创建设备时,就可以使用在控制台上定义的产品模型。
2.2.1 添加服务
2.2.2 添加属性
2.2.3 添加命令
2.3 SDK下载及修改
SDK下载: https://support.huaweicloud.com/sdkreference-iothub/iot_10_1002.html
选择 IoT Device SDK Tiny (C)
IoT Device SDK Tiny(下文统一简称SDK)是部署在具备广域网能力、对功耗/存储/计算资源有苛刻限制的终端设备上的轻量级互联互通中间件,您只需调用API接口,便可实现设备快速接入到物联网平台以及数据上报和命令接收等功能。
SDK提供端云协同能力,集成了MQTT、LwM2M、CoAP、mbedtls、LwIP 全套 IoT 互联互通协议栈,且在这些协议栈的基础上,提供了开放 API,用户只需关注自身的应用,而不必关注协议内部实现细节,直接使用SDK封装的API,通过连接、数据上报、命令接收和断开四个步骤就能简单快速地实现与华为OceanConnect云平台的安全可靠连接。使用SDK,用户可以大大减少开发周期,聚焦自己的业务开发,快速构建自己的产品。
三、生成连接信息
点击新增测试设备,输入设备名称和设备标识码
将创建成功的设备ID和设备密钥放入以下网址中: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/ 生成设备对接信息 稍后在代码中替换
ClientId 一机一密的设备clientId由4个部分组成:设备ID、设备身份标识类型、密码签名类型、时间戳,通过下划线“_”分隔。
设备ID: 指设备在平台成功注册后生成的唯一设备标识,通常由设备的产品ID和设备的NodeId通过分隔符“_”拼装而来。
设备身份标识类型: 固定值为0,表示设备ID。
密码签名类型: 长度1字节,当前支持2种类型:
“0”代表HMACSHA256不校验时间戳。
“1”代表HMACSHA256校验时间戳。
时间戳: 为设备连接平台时的UTC时间,格式为YYYYMMDDHH,如UTC 时间2018/7/24 17:56:20 则应表示为2018072417。
四、API说明
以下OC MQTT接口位于 LiteOS_Lab/iot_link/oc/oc_mqtt/oc_mqtt_al/oc_mqtt_al.h。
4.1 oc_mqtt_init
以下OC MQTT接口位于 LiteOS_Lab/iot_link/oc/oc_mqtt/oc_mqtt_profile_v5/oc_mqtt_profile.h。
4.2 oc_mqtt_profile_cmdresp
4.3 oc_mqtt_profile_propertyreport
五、工程代码
BearPi-HM_Nano开发板WiFi编程开发——MQTT连接华为IoT平台
六、软件设计
6.1 连接平台
在连接平台前需要获取CLIENT_ID、USERNAME、PASSWORD,访问这里,填写注册设备后生成的设备ID(DeviceId)和密钥(DeviceSecret),生成连接信息(ClientId、Username、Password)。
#define CLIENT_ID "625bcba4861486498f176e66_20220503_0_0_2022050307"#define USERNAME "625bcba4861486498f176e66_20220503"#define PASSWORD "53005e789e39ea8ba044cdb9e8b764f1f5695e7f947b47b249909180658a0fc1"
在 OC_Demo()
函数中首先用 osMessageQueueNew()
创建了消息队列 mid_MsgQueue,然后创建了两个任务 task_main_entry
和 task_sensor_entry
。
static void OC_Demo(void){ mid_MsgQueue = osMessageQueueNew(MSGQUEUE_OBJECTS, 10, NULL); if (mid_MsgQueue == NULL) { printf("Falied to create Message Queue!\n"); } osThreadAttr_t attr; attr.name = "task_main_entry"; attr.attr_bits = 0U; attr.cb_mem = NULL; attr.cb_size = 0U; attr.stack_mem = NULL; attr.stack_size = 10240; attr.priority = 24; if (osThreadNew((osThreadFunc_t)task_main_entry, NULL, &attr) == NULL) { printf("Falied to create task_main_entry!\n"); } attr.stack_size = 2048; attr.priority = 25; attr.name = "task_sensor_entry"; if (osThreadNew((osThreadFunc_t)task_sensor_entry, NULL, &attr) == NULL) { printf("Falied to create task_sensor_entry!\n"); }}
在 task_main_entry()
函数中首先使用 WifiConnect()
连接WIFI,然后 oc_mqtt_init()
初始化OC MQTT,和 oc_set_cmd_rsp_cb()
设置接收命令的回调函数。随后在 while 循环里不断获取消息,如果是 en_msg_cmd
命令消息则调用 deal_cmd_msg()
处理命令内容,如果是 en_msg_report
上报消息则调用 deal_report_msg()
上报数据。
static int task_main_entry(void){ app_msg_t *app_msg; uint32_t ret = WifiConnect("test", "12345678"); device_info_init(CLIENT_ID, USERNAME, PASSWORD); 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;}
6.2 推送数据
在 task_sensor_entry()
函数中先进行传感器引脚初始化,然后不断读取传感器数据,然后通过消息队列发送到 task_main_entry()
函数。
static int task_sensor_entry(void){ app_msg_t *app_msg; E53_IA1_Data_TypeDef data; E53_IA1_Init(); while (1) { E53_IA1_Read_Data(&data); app_msg = malloc(sizeof(app_msg_t)); printf("SENSOR:lum:%.2f temp:%.2f hum:%.2f\r\n", data.Lux, data.Temperature, data.Humidity); if (NULL != app_msg) { app_msg->msg_type = en_msg_report; app_msg->msg.report.hum = (int)data.Humidity; app_msg->msg.report.lum = (int)data.Lux; app_msg->msg.report.temp = (int)data.Temperature; if (0 != osMessageQueuePut(mid_MsgQueue, &app_msg, 0U, 0U)) { free(app_msg); } } sleep(3); } return 0;}
在数据上报函数 deal_report_msg()
中,首先对我们之前在云平台设置的服务和属性进行 JSON 数据拼装,服务ID名 Agriculture
要相对应。
要注意,如果拼接后面还有属性则 .nxt
填下一个属性,否则填 NULL。
然后通过 oc_mqtt_profile_propertyreport()
上报数据。
static void deal_report_msg(report_t *report){ 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; 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;}
6.3 命令接收
在 oc_cmd_rsp_cb()
接收命令的回调函数中,收到云平台命令则通过消息队列发送到 task_main_entry()
函数,然后交给 deal_cmd_msg()
处理命令内容。
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\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;}
在命令解析函数 deal_cmd_msg()
中,对 JSON数据进行解析,对应之前在云平台设置的命令。
///< COMMAND DEAL#include <cJSON.h>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;}
七、测试结果
上报数据
下发命令
• 由 Leung 写于 2022 年 5 月 3 日
• 参考:【鸿蒙2.0设备开发教程】小熊派HarmonyOS 鸿蒙·季 开发教程