简介

设备接入服务(IoT Device Access)是华为云的物联网平台,提供海量设备连接上云、设备和云端双向消息通信、批量设备管理、远程控制和监控、OTA升级、设备联动规则等能力,并可将设备数据灵活流转到华为云其他服务,帮助物联网行业用户快速完成设备联网及行业应用集成。
无论是集成LiteOS的物联网终端设备,还是集成HarmonyOS的终端设备,都可以接入华为云IoT平台,实现属性上报与命令下发。
为此,您需要:

实验开始之前,您需要确认已完成开发环境搭建,可参考HarmonyOS智能台灯案例(开发环境搭建)

您将开发什么

在本次Codelab中,您将使用DevEco Device Tool和设备侧MQTT接口,开发设备端功能,实现:

您将会学到什么

硬件要求

软件要求

需要的知识点

嵌入式C语言基础开发能力。

  1. 访问设备接入IoTDA服务产品首页,链接如下:https://www.huaweicloud.com/product/iothub.html
  2. 单击"立即使用",进入如下页面,即表示自助开通服务成功。
  3. 进入"平台接入地址"页面,记录MQTT协议设备接入地址与端口号。

创建产品

产品模型是用来描述设备能力的文件,通过JSON的格式定义了设备的基本属性、上报数据和下发命令的消息格式。定义产品模型,即在物联网平台构建一款设备的抽象模型,使平台理解该款设备支持的属性信息。

  1. 进入设备接入服务控制台,选择左侧导航栏"产品"进入页面。
  2. 单击右上角的"创建产品"。
  3. 按照页面提示填写信息。
    • 所属资源空间:选择默认资源空间
    • 产品名称:自定义,如SmartLight
    • 协议类型:MQTT
    • 数据格式:JSON
    • 厂商名称:自定义,如Huawei
    • 所属行业:无
    • 设备类型:自定义,如SmartLight
  4. 单击"确定",产品创建成功。
  5. 单击"查看详情",进入产品详情页面。

定义产品模型

  1. 在产品详情页"模型定义"页签,单击"自定义模型",弹出"添加服务"窗口。
  2. 请按如下要求,填写服务ID和服务类型。
    • 服务ID:Light(必须一致)
    • 服务类型:Sensor(可自定义)
  3. 单击"确定"。
  1. 在"Light"右侧单击"添加属性",弹出"新增属性"窗口。
  2. 填写属性关联参数信息:
    • 属性名称:Luminance
    • 数据类型:int(整型)
    • 其他保持默认值。
  3. 单击"确定"。
  4. 重复以上步骤,添加如下属性:
    • 属性名称:LightStatus
    • 数据类型:string(字符串)
    • 长度:3
    • 其他保持默认值。
  1. 在"Light"右侧单击"添加命令",弹出"新增命令"窗口。
  2. 填写命令名称为Light_Control_Led。
  3. 单击"新增输入参数",弹出"新增参数"窗口。
  4. 填写参数信息:
    • 参数名称:Led
    • 数据类型:string(字符串)
    • 长度:3
    • 枚举值:ON,OFF
  5. 单击"确定",关闭"新增参数"窗口。
  6. 单击"确定",关闭"新增命令"窗口。

需要注册设备,获取设备ID和密钥。

  1. 选择左侧导航栏"设备 > 所有设备"进入页面。
  2. 单击右上角的"注册设备",弹出"单设备注册"窗口。
  3. 填写设备信息:
    • 所属资源空间:选择默认资源空间
    • 所属产品:选择刚创建的产品,如SmartLight
    • 设备标识码:自定义,如20210826081000
    • 设备名称:自定义,如DeskLamp
    • 设备认证类型:密钥
    • 密钥/确认密钥:自定义,如12345678
  4. 单击"确定",完成设备注册。
  5. 记录设备ID和设备密钥,单击"保存并关闭"。
  6. 查看设备列表,注册成功的设备显示为"未激活"状态。

获取源码

  1. 打开windows命令窗口(Win+R),使用docker命令进入Linux虚拟机容器。
    docker run -it -v d:\harmonyos:/home/openharmony swr.cn-south-1.myhuaweicloud.com/openharmony-docker/openharmony-docker:0.0.5
  2. 在容器命令行输入命令:
    mkdir code01; cd code01 hpm init -t default hpm install @bearpi/bearpi_hm_nano

    执行命令后,进入代码下载和解压阶段,可能会耗费几分钟时间,出现如下界面表示代码下载完成:

    下载完成后,会在D盘下,产生一个harmonyos目录,打开harmonyos目录并将code01文件夹拖拽到Visual Studio Code中打开。

    源码目录介绍

    目录名

    描述

    applications

    应用程序样例

    base

    基础软件服务子系统集&硬件服务子系统集

    build

    组件化编译、构建和配置脚本

    docs

    说明文档

    domains

    增强软件服务子系统集

    drivers

    驱动子系统

    foundation

    系统基础能力子系统集

    kernel

    内核子系统

    prebuilts

    编译器及工具链子系统

    test

    测试子系统

    third_party

    开源第三方组件

    utils

    常用的工具集

    vendor

    厂商提供的软件

    build.py

    编译脚本文件

新建项目

  1. 在./applications/BearPi/BearPi-HM_Nano/sample路径下新建一个e53_sc1_smart_light_sample目录,用于存放业务源码文件。
  2. 在./applications/BearPi/BearPi-HM_Nano/sample/e53_sc1_smart_light_sample路径下新建一个iot_cloud_oc_sample.c文件,该文件为业务源码文件。
    新建业务入口函数HelloWorld,并实现业务逻辑。并在代码最下方,使用HarmonyOS启动恢复模块接口APP_FEATURE_INIT()启动业务。(APP_FEATURE_INIT定义在ohos_init.h文件中)
    #include #include "ohos_init.h" void Hello_World(void) { printf("Hello World!\r\n"); } APP_FEATURE_INIT(Hello_World);
  3. 在./applications/BearPi/BearPi-HM_Nano/sample/e53_sc1_smart_light_sample路径下新建一个BUILD.gn文件,该文件为业务源码编译脚本。
    添加如下代码
    static_library("app") { sources = [ "iot_cloud_oc_sample.c" ] include_dirs = [ "//utils/native/lite/include" ] }
    • static_library中指定业务模块的编译结果,为静态库文件libapp.a,开发者根据实际情况完成填写。
    • sources中指定静态库.a所依赖的.c文件及其路径,若路径中包含"//"则表示绝对路径(此处为代码根路径),若不包含"//"则表示相对路径。
    • include_dirs中指定source所需要依赖的.h文件路径。
  4. 在./applications/BearPi/BearPi-HM/sample下的BUILD.gn文件中添加如下代码。
    import("//build/lite/config/component/lite_component.gni") lite_component("app") { features = [ "e53_sc1_smart_light_sample:app", ] }
    • e53_sc1_smart_light_sample是相对路径,指向./applications/BearPi/BearPi-HM/sample/e53_sc1_smart_light_sample/BUILD.gn。
    • app是目标,指向./applications/BearPi/BearPi-HM/sample/e53_sc1_smart_light_sample/BUILD.gn中的static_library("app")。

编译代码

  1. 在code01目录下,执行hpm的编译命令
    hpm dist

    编译过程中会打印相应的编译日志,编译完成后会提示build success编译完成信息。

    编译完成后,在code01目录下会产生out目录,存放着编译完成的二进制文件。

    通过TypeC数据线,把电脑与BearPi-HM Nano连接。
    打开电脑设备管理器,查看开发板的串口(请先安装软件准备中的CH341SER.EXE驱动)

烧录代码

HUAWEI DevEco Device Tool智能设备一站式集成开发环境支持在windows环境下烧录代码。
首先找到D:\harmonyos\code01\out\bearpi_hm_nano\bearpi_hm_nano目录下编译产生的二进制文件Hi3861_wifiiot_app_allinone.bin
根据驱动提示的串口信息设置烧录选项

  1. 打开Visual Studio Code默认会进入DevEco Device Tool首页:
  2. 点击新建DevEco Project,创建新工程,跳出的配置项向导中,进行如下配置(工程名可自己配置)后创建:
  3. 创建后,进入工程配置页面,然后设置烧录选项
    在"hi3861"页签,设置烧录选项,包括upload_port、upload_protocol和upload_partitions:
    • upload_port:选择步骤2中查询的串口号
    • upload_protocol:选择烧录协议,固定选择"hiburn-serial"
    • upload_partitions:选择待烧录的文件,固定选择"partition:hi3861_app"

    在"Partition Configuration"页签,设置烧录二进制文件。
    点击"Binary"选择框下,选择编译产生的二进制文件"Hi3861_wifiiot_app_allinone.bin",并设置对应二进制文件。

    设置完成,点击"Save"按钮,然后单击"Open"按钮打开工程。

  4. 打开工程文件,在DevEco Device Tool界面的"PROJECT TASKS"中,点击env:hi3861下的Upload按钮,启动烧录。
  5. 启动烧录后,显示如下提示信息时,请按开发板上的RST按钮重启开发板。

    重新上电后,启动烧录,界面提示如下信息时,表示烧录成功。

查看串口日志

  1. Visual Studio Code主界面,点击"Serial Monitor"工具按钮,即可连接3861设备串口。

  2. 点击Reset,会打印初始化信息,并输出"Hello World!"。
  1. 在./applications/BearPi/BearPi-HM_Nano/sample/D9_iot_cloud_oc_light路径下,拷贝include文件夹,粘贴到./applications/BearPi/BearPi-HM_Nano/sample/e53_sc1_smart_light_sample目录下。
  2. 在./applications/BearPi/BearPi-HM_Nano/sample/D9_iot_cloud_oc_light路径下,拷贝src文件夹,粘贴到./applications/BearPi/BearPi-HM_Nano/sample/e53_sc1_smart_light_sample目录下。
  3. 在./applications/BearPi/BearPi-HM_Nano/sample/e53_sc1_smart_light_sample路径下,编辑iot_cloud_oc_sample.c文件。
    添加业务代码:
    #include #include #include #include #include "ohos_init.h" #include "cmsis_os2.h" #include "wifi_connect.h" #include #include #include #include "E53_SC1.h" #include #include #define CONFIG_WIFI_SSID "ChinaNet-SecS" //修改为自己的WiFi 热点账号 #define CONFIG_WIFI_PWD "rupzd252" //修改为自己的WiFi 热点密码 #define CONFIG_APP_SERVERIP "121.36.42.100" #define CONFIG_APP_SERVERPORT "1883" #define CONFIG_APP_DEVICEID "6124ad7a0ad1ed02866267bb_20210826081000" //替换为注册设备后生成的deviceid #define CONFIG_APP_DEVICEPWD "12345678" //替换为注册设备后生成的密钥 #define CONFIG_APP_LIFETIME 60 ///< seconds #define CONFIG_QUEUE_TIMEOUT (5*1000) #define MSGQUEUE_OBJECTS 16 // number of Message Queue Objects typedef enum { en_msg_cmd = 0, en_msg_report, }en_msg_type_t; typedef struct { char *request_id; char *payload; } cmd_t; typedef struct { int lum; } report_t; typedef struct { en_msg_type_t msg_type; union { cmd_t cmd; report_t report; } msg; } app_msg_t; typedef struct { queue_t *app_msg; int connected; int led; } app_cb_t; static app_cb_t g_app_cb; //use this function to push all the message to the buffer static int msg_rcv_callback(oc_mqtt_profile_msgrcv_t *msg) { int ret = 0; char *buf; int buf_len; app_msg_t *app_msg; if((NULL == msg)|| (msg->request_id == NULL) || (msg->type != EN_OC_MQTT_PROFILE_MSG_TYPE_DOWN_COMMANDS)){ return ret; } buf_len = sizeof(app_msg_t) + strlen(msg->request_id) + 1 + msg->msg_len + 1; buf = malloc(buf_len); if(NULL == buf){ return ret; } app_msg = (app_msg_t *)buf; buf += sizeof(app_msg_t); app_msg->msg_type = en_msg_cmd; app_msg->msg.cmd.request_id = buf; buf_len = strlen(msg->request_id); buf += buf_len + 1; memcpy(app_msg->msg.cmd.request_id, msg->request_id, buf_len); app_msg->msg.cmd.request_id[buf_len] = '\0'; buf_len = msg->msg_len; app_msg->msg.cmd.payload = buf; memcpy(app_msg->msg.cmd.payload, msg->msg, buf_len); app_msg->msg.cmd.payload[buf_len] = '\0'; ret = queue_push(g_app_cb.app_msg,app_msg,10); if(ret != 0){ free(app_msg); } return ret; } static int task_main_entry(void) { app_msg_t *app_msg; uint32_t ret ; WifiConnect(CONFIG_WIFI_SSID, CONFIG_WIFI_PWD); dtls_al_init(); mqtt_al_init(); oc_mqtt_init(); g_app_cb.app_msg = queue_create("queue_rcvmsg",10,1); if(NULL == g_app_cb.app_msg){ printf("Create receive msg queue failed"); } oc_mqtt_profile_connect_t connect_para; (void) memset( &connect_para, 0, sizeof(connect_para)); connect_para.boostrap = 0; connect_para.device_id = CONFIG_APP_DEVICEID; connect_para.device_passwd = CONFIG_APP_DEVICEPWD; connect_para.server_addr = CONFIG_APP_SERVERIP; connect_para.server_port = CONFIG_APP_SERVERPORT; connect_para.life_time = CONFIG_APP_LIFETIME; connect_para.rcvfunc = msg_rcv_callback; connect_para.security.type = EN_DTLS_AL_SECURITY_TYPE_NONE; ret = oc_mqtt_profile_connect(&connect_para); if((ret == (int)en_oc_mqtt_err_ok)){ g_app_cb.connected = 1; printf("oc_mqtt_profile_connect succed!\r\n"); } else { printf("oc_mqtt_profile_connect faild!\r\n"); } return 0; } static void OC_Demo(void) { 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"); } } APP_FEATURE_INIT(OC_Demo);
  4. 在./applications/BearPi/BearPi-HM_Nano/sample/e53_sc1_smart_light_sample路径下,编辑BUILD.gn文件。
    添加如下代码:
    static_library("app") { sources = [ "iot_cloud_oc_sample.c", "src/wifi_connect.c", "src/E53_SC1.c" ] cflags = [ "-Wno-unused-variable" ] cflags += [ "-Wno-unused-but-set-variable" ] include_dirs = [ "//utils/native/lite/include", "//kernel/liteos_m/components/cmsis/2.0", "//base/iot_hardware/interfaces/kits/wifiiot_lite", "//foundation/communication/interfaces/kits/wifi_lite/wifiservice", "//vendor/hisi/hi3861/hi3861/third_party/lwip_sack/include/", "//third_party/cJSON", "//third_party/iot_link/oc_mqtt/oc_mqtt_al", "//third_party/iot_link/oc_mqtt/oc_mqtt_profile_v5", "//third_party/iot_link/inc", "//third_party/iot_link/queue", "include" ] deps = [ "//third_party/iot_link:iot_link", ] }
  5. 重新编译烧录,复位开发板,等待5秒钟,查看设备状态变更为"在线"状态。
  1. 在./applications/BearPi/BearPi-HM_Nano/sample/e53_sc1_smart_light_sample路径下,编辑iot_cloud_oc_sample.c文件。
    在msg_rcv_callback函数前面,新增属性上报处理函数:
    static void deal_report_msg(report_t *report) { oc_mqtt_profile_service_t service; oc_mqtt_profile_kv_t luminance; oc_mqtt_profile_kv_t led; if(g_app_cb.connected != 1){ return; } service.event_time = NULL; service.service_id = "Light"; service.service_property = &luminance; service.nxt = NULL; 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 = NULL; oc_mqtt_profile_propertyreport(NULL,&service); return; }

    修改task_main_entry函数,添加红色标识代码段:

    static int task_main_entry(void) { app_msg_t *app_msg; uint32_t ret ; WifiConnect(CONFIG_WIFI_SSID, CONFIG_WIFI_PWD); dtls_al_init(); mqtt_al_init(); oc_mqtt_init(); g_app_cb.app_msg = queue_create("queue_rcvmsg",10,1); if(NULL == g_app_cb.app_msg){ printf("Create receive msg queue failed"); } oc_mqtt_profile_connect_t connect_para; (void) memset( &connect_para, 0, sizeof(connect_para)); connect_para.boostrap = 0; connect_para.device_id = CONFIG_APP_DEVICEID; connect_para.device_passwd = CONFIG_APP_DEVICEPWD; connect_para.server_addr = CONFIG_APP_SERVERIP; connect_para.server_port = CONFIG_APP_SERVERPORT; connect_para.life_time = CONFIG_APP_LIFETIME; connect_para.rcvfunc = msg_rcv_callback; connect_para.security.type = EN_DTLS_AL_SECURITY_TYPE_NONE; ret = oc_mqtt_profile_connect(&connect_para); if((ret == (int)en_oc_mqtt_err_ok)){ g_app_cb.connected = 1; printf("oc_mqtt_profile_connect succed!\r\n"); } else { printf("oc_mqtt_profile_connect faild!\r\n"); } while (1) { app_msg = NULL; (void)queue_pop(g_app_cb.app_msg,(void **)&app_msg,0xFFFFFFFF); if (NULL != app_msg) { switch (app_msg->msg_type) { case en_msg_report: deal_report_msg(&app_msg->msg.report); break; default: break; } free(app_msg); } } return 0; }

    在task_main_entry函数后面,新增传感器数采处理函数:

    static int task_sensor_entry(void) { app_msg_t *app_msg; float Lux; E53_SC1_Init(); while (1) { Lux = E53_SC1_Read_Data(); app_msg = malloc(sizeof(app_msg_t)); printf("Lux data:%f\r\n", Lux); if (NULL != app_msg) { app_msg->msg_type = en_msg_report; app_msg->msg.report.lum = (int)Lux; if(0 != queue_push(g_app_cb.app_msg,app_msg,CONFIG_QUEUE_TIMEOUT)){ free(app_msg); } } sleep(3); } return 0; }

    修改OC_Demo主函数,添加红色标识代码段:

    static void OC_Demo(void) { 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 = 4096; 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"); } }
  2. 重新编译烧录,复位开发板,等在设备在线;查看设备详情页,显示设备上报属性。
  1. 在./applications/BearPi/BearPi-HM_Nano/sample/e53_sc1_smart_light_sample路径下,编辑iot_cloud_oc_sample.c文件。
    在msg_rcv_callback函数后面,新增命令响应处理函数:<
    ///< COMMAND DEAL #include 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), "Light_Control_Led")) { obj_paras = cJSON_GetObjectItem(obj_root, "paras"); if (NULL == obj_paras) { goto EXIT_OBJPARAS; } obj_para = cJSON_GetObjectItem(obj_paras, "Led"); 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("Led On!\r\n"); } else { g_app_cb.led = 0; Light_StatusSet(OFF); printf("Led Off!\r\n"); } 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; }

    修改task_main_entry函数,添加红色标识代码段:

    static int task_main_entry(void) { app_msg_t *app_msg; uint32_t ret ; WifiConnect(CONFIG_WIFI_SSID, CONFIG_WIFI_PWD); dtls_al_init(); mqtt_al_init(); oc_mqtt_init(); g_app_cb.app_msg = queue_create("queue_rcvmsg",10,1); if(NULL == g_app_cb.app_msg){ printf("Create receive msg queue failed"); } oc_mqtt_profile_connect_t connect_para; (void) memset( &connect_para, 0, sizeof(connect_para)); connect_para.boostrap = 0; connect_para.device_id = CONFIG_APP_DEVICEID; connect_para.device_passwd = CONFIG_APP_DEVICEPWD; connect_para.server_addr = CONFIG_APP_SERVERIP; connect_para.server_port = CONFIG_APP_SERVERPORT; connect_para.life_time = CONFIG_APP_LIFETIME; connect_para.rcvfunc = msg_rcv_callback; connect_para.security.type = EN_DTLS_AL_SECURITY_TYPE_NONE; ret = oc_mqtt_profile_connect(&connect_para); if((ret == (int)en_oc_mqtt_err_ok)){ g_app_cb.connected = 1; printf("oc_mqtt_profile_connect succed!\r\n"); } else { printf("oc_mqtt_profile_connect faild!\r\n"); } while (1) { app_msg = NULL; (void)queue_pop(g_app_cb.app_msg,(void **)&app_msg,0xFFFFFFFF); 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; }
  2. 重新编译烧录,复位开发板,等待设备显示在线。
  3. 进入设备详情页,在命令页签下,选择同步命令下发。
    单击"命令下发",选择命令并设置Led:ON,单击"确定"。
  4. 查看开发板E53_SC1扩展板上的灯亮了。
  5. 下发命令Led:OFF,查看开发板E53_SC1扩展板上的灯灭了。

干得好,您已经成功完成了codelab并学到了:

您可以阅读下面链接,了解更多相关的信息:

相关文档
本Codelab中所用Demo源码下载地址如下:

源码下载

Code copied