梅科尔工作室-#14天鸿蒙设备开发实战#设备联网上云笔记 原创 精华
@toc
HarmonyOS网络应用开发
对接华为IoT平台
参考:
- HarmonyOS网络应用开发—对接华为IoT平台_哔哩哔哩_bilibili
- applications/BearPi/BearPi-HM_Nano/sample/D6_iot_cloud_oc/README.md · 小熊派开源社区/BearPi-HM_Nano - 码云 - 开源中国 (gitee.com)
- 成长地图_设备接入 IoTDA_华为云 (huaweicloud.com)
- cJSON 解析 - 简书 (jianshu.com)
- cJSON库学习总结+使用例程 | 码农家园 (codenong.com)
华为IoT平台介绍
华为云物联网平台即华为设备接入服务(IoT Device Access),提供海量设备连接上云、设备和云端双向消息通信、批量设备管理、远程控制和监控、OTA升级、设备联动规则等能力,并可将设备数据灵活流转到华为云其他服务,帮助物联网行业用户快速完成设备联网及行业应用集成。
华为IoT平台产品创建
可以在华为IoT平台创建产品模型,用于描述设备具备的能力和特性。开发者通过定义产品模型,在物联网平台构建一款设备的抽象模型,使平台理解该款设备支持的服务、属性、命令等信息,如颜色、开关等。
本次以智慧农业产品为案例,最终实现效果是可以接收环境温度(Temperature)、湿度(Humidity)、光照强度(Luminance)、补光灯状态(LightStatus)、电机状态(MotorStatus),可以控制补光灯开关和电机的开关,通过MQTT协议对接华为IoT平台。
服务信息:
服务ID | 服务类型 |
---|---|
Agriculture | Senser |
属性信息:
属性名称 | 数据类型 |
---|---|
Temperature | int |
Humidity | int |
Luminance | int |
LightStatus | string |
MotorStatus | string |
命令信息:
命令名称 | 参数名称 | │数据类型│ | 长 度 | 枚举 |
---|---|---|---|---|
Agriculture_Control_light | Light | string | 3 | ON,OFF |
Agriculture_Control_Motor | Motor | string | 3 | ON,OFF |
设备对接华为IoT平台
本次案例将演示如何在BearPi-HM_Nano开发板上使用MQTT协议连接华为IoT平台,需要将E53_IA1智慧农业扩展板与BearPi-HM_Nano开发板安装在一起。E53_IA1智慧农业扩展板购买地址为:小熊派BearPi开发板智慧农业案例扩展板E53_IA1-淘宝网 (taobao.com)
安装效果图如下:
华为云IoT平台创建产品
-
点击立即使用
-
控制台选择华北-北京四
-
可以由MQTT协议的域名生成ip地址,在命令行工具中
ping 域名
,通过ip地址和端口来连接IoT平台(似乎没有用到,可省略) -
创建一个产品,并输入相应信息
-
产品创建完成
产品服务能力开发
-
创建一个服务
-
为服务添加属性和命令,属性是指设备能上报哪些数据,命令是指设备能下发哪些命令
-
填加属性
需要依次添加温度(Temperature)、湿度(Humidity)、光照强度(Luminance)、补光灯状态(LightStatus)、电机状态(MotorStatus)5个属性。补光灯状态(LightStatus)、电机状态(MotorStatus)的数据类型需要选择string(字符串),之后一些选项会改变,其中长度为3(补光灯状态和电机状态有ON和OFF两种类型,最大长度是3)。
-
添加命令
需要添加补光灯状态控制(Agriculture_Control_light)、电机状态控制(Agriculture_Control_Motor)2个命令。
-
-
产品服务能力开发完毕
生成设备对接信息
-
新增测试设备,最近点击确定之后会弹出一个弹窗,需要复制其中的设备ID和设备秘钥,后续激活设备需要使用
-
生成设备对接信息网址
-
打开网址:Huaweicloud IoTDA Mqtt ClientId Generator (myhuaweicloud.com)
-
输入设备ID和设备秘钥,需要将生成的设备信息保存,后续需要填入代码中
-
设备开发
打开案例源码,源码在applications\BearPi\BearPi-HM_Nano\sample\D6_iot_cloud_oc\iot_cloud_oc_sample.c
路径下,以下添加了部分注释:
/*日志中实时打印此时的温度、湿度、光照强度,并且在华为云IoT平台新增的测试设备调试页面,数据接收窗口可以看到接收的数据;在命令发送窗口,可以进行命令的发送,如打开补光灯命令发送后,开发板的E53_IA1智慧农业扩展板上的补光灯会被点亮*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "wifi_connect.h"
#include "lwip/sockets.h"
#include "oc_mqtt.h"
#include "E53_IA1.h"
#define MSGQUEUE_OBJECTS 16 // number of Message Queue Objects
typedef struct
{ // object data type
char *Buf;
uint8_t Idx;
} MSGQUEUE_OBJ_t;
MSGQUEUE_OBJ_t msg;
osMessageQueueId_t mid_MsgQueue; // message queue id
#define CLIENT_ID "5fc7152fb4ec2202e99df7bf_2020111409_0_0_2020120205" // 此处为生成的设备对接信息ClientId
#define USERNAME "5fc7152fb4ec2202e99df7bf_2020111409" // 此处为生成的设备对接信息Username
#define PASSWORD "9b501c0ce96e0d79a071703b870060d939e95715397c6cb5dee90a93a38dd288" // 此处为生成的设备对接信息Password
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;
int temp;
int hum;
} report_t;
typedef struct
{
en_msg_type_t msg_type;
union
{
cmd_t cmd;
report_t report;
} msg;
} app_msg_t;
typedef struct
{
int connected;
int led;
int motor;
} app_cb_t;
static app_cb_t g_app_cb;
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"; // 服务id,需要和产品服务能力开发是填写的服务id一致
service.service_property = &temperature; // 配置文件中的属性,不能为NULL
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;
}
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;
}
///< 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"); // 通过command_name查找对应的obj_root对象的元素指针
if (NULL == obj_cmdname)
{
goto EXIT_CMDOBJ;
}
if (0 == strcmp(cJSON_GetStringValue(obj_cmdname), "Agriculture_Control_light")) // 补光灯状态控制,strcmp()用于比较字符串,其中Agriculture_Control_light为添加命令时输入的命令名称,cJSON_GetStringValue()用于检查该项是否为字符串并返回其valuestring,
{
obj_paras = cJSON_GetObjectItem(obj_root, "paras");
if (NULL == obj_paras)
{
goto EXIT_OBJPARAS;
}
obj_para = cJSON_GetObjectItem(obj_paras, "Light"); // Light为添加命令时新增参数输入的参数名称
if (NULL == obj_para)
{
goto EXIT_OBJPARA;
}
///< operate the LED here
if (0 == strcmp(cJSON_GetStringValue(obj_para), "ON")) // 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;
}
// 命令下发处理、消息上报处理
static int task_main_entry(void)
{
app_msg_t *app_msg;
uint32_t ret = WifiConnect("Hold", "0987654321"); // 连接WiFi,参数分别是名称和password
device_info_init(CLIENT_ID, USERNAME, PASSWORD); // 配置设备信息
oc_mqtt_init(); // oc mqtt客户端初始化
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); // 消息上报处理,将传感器的数据传递到IoT平台
break;
default:
break;
}
free(app_msg);
}
}
return 0;
}
// 采集传感器数据
static int task_sensor_entry(void)
{
app_msg_t *app_msg;
E53_IA1_Data_TypeDef data;
E53_IA1_Init(); // 初始化E53_IA1
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;
}
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");
}
}
APP_FEATURE_INIT(OC_Demo);
其中的WiFi名称及password、设备对接信息需要自行修改
修改applications\BearPi\BearPi-HM_Nano\sample
路径下BUILD.gn,指定 oc_mqtt
,编译、烧录,打印日志
日志中实时打印此时的温度、湿度、光照强度,并且在华为云IoT平台新增的测试设备调试页面,数据接收窗口可以看到接收的数据;在命令发送窗口,可以进行命令的发送,如打开补光灯命令发送后,开发板的E53_IA1智慧农业扩展板上的补光灯会被点亮。
感悟
本部分是14天鸿蒙设备开发实战最后一章(虽然学了30天),学习完之后还是挺有成就感的。并且通过本章也直接让我感受到了小熊派开发板在实际生活中的应用,智慧农业只是其中一个方面,根据不同的应用场景有不同的扩展板可以进行使用,这就大大降低了开发者的门槛。通过这一个月的学习,也让我认识到了我的基础知识需要加强,需要学习的地方还有很多。未来各种智慧场景的应用一定会更加广泛,如果想要出一份力,我目前的水平远远不够,还需要提高自己,学习的路很长,需要慢慢的走,关键是要坚持下去,加油加油!!!。
一路跟着楼主的文章走来,感叹楼主认真的学习态度,向楼主学习。
谢谢鼓励