【本文正在参加2023年第一期优质创作者激励计划】
基于OpenHarmony的储物精灵
一.项目简介
1.产品描述
基于OpenHarmony的智能柜物管理系统,可用于不同场景的环境下通过终端检索到物品的出入库信息与通过OpenHarmony特性来赋能储物精灵。
储物精灵NFC版:这是一个智慧储物系统,终端可以检索到物品的拿放入信息,包括名称以及放入时间与备注、位置等等。
储物精灵Pro版:通过镜头对监控画面抓取后上传到云端逐帧比对,此功能在红外感应人体靠近后激活。可以在终端检索到出入库信息。
功能阐述:密码保险与开锁,NFC认证刷卡,智能门轨等(详见下述解析)等
2.技术要求
掌握C, JS, Java语言
掌握原子化服务原理(Fa ability)
熟悉 Git 等代码版本管理工具
熟悉OpenHarmony的设备端开发,包括构建系统
熟悉IoT物联网协议(MQTT)
熟悉VS code与Deveco studo软件
3.资源获取
在Gitee上获取OpenHarmony源码(本次案例用3.0版本作为演示)
在Gitee上获取MQTT协议与VS code的编译工具组件
操作系统Ubuntu华为云中国镜像
https://www.huaweicloud.com/theme/77291-1-U
分布式数据对象
https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-data-distributedobject.md
分布式数据管理
https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-distributed-data.md#kvstoreresultset8
二.环境搭建
1.软件准备
VS code:https://device.harmonyos.com/cn/develop/ide
端口驱动官网:http://www.wch.cn/downloads/CH341SER_EXE.html
选择合适的驱动下载后通过USB线即可让板子成功与电脑连接(可以去设备管理器去看看这个驱动有没有安装成功)
2.工具介绍
2.1 VS code:
华为把一个集成的硬件编制软件作为插件放在了这个代码编辑器里,里面包含代码编辑,编译,烧录等功能。是一个集成环境。
2.2 Deveco studio tool:
环境配置组件与软件插件:第一次下载软件后需要自行载入编译组件。相关组件(这里所载入的插件版本号与描述可以随意填,不影响正常使用)下载完通过点击VS code左侧的插件标识后在QUICK ACCESS/主界面/工程设置里点击<添加用户组件>即可添加。
2.3 关于插件:
必须的插件:C/C++,C/C++ GNU Global,Chinese (Simplified),CodeLLDB。
推荐的插件:GN , Better C++ Syntax, CMake Tools, Remote – SSH。
2.4 使用到的开发板:
深开鸿KHDVK-3861B,润和DAYU,润和hispark AI Camera
2.5 关于环境:
操作系统:Ubuntu 编译构建:Python
包管理工具:HPM NPM 环境:Node.js
OH版本:3.0 USB串口驱动:CH341SER.exe
烧录软件:Hiburn 开发板:润和HI3861
本地ssh:finalshell ssh文件对比:Beyond Conpare
华为硬件开发工具:Visual Studio Code(DevEco Device Tool)
华为系统开发工具:Deveco Studio *手动在设置中下载SDK
2.6 虚拟机:
Ubuntu(华为的硬件开发一般都在此linux环境下进行)
虚拟机Vmware:下载之后使用上述提到的华为云中国镜像。
下载VS code的Linux版与OpenHarmony源码。
2.7 虚拟机环境:
将Ubuntu Shell环境修改为bash:ls -l /bin/sh
下载华为硬件编制插件(Device tool)
2.8 HB编译插件
Hb编译插件(可以在代码编辑完之后使用命令行直接进行编译)
安装:python3 -m pip install --user ohos-build
变量:vim ~/.bashrc
更新变量:source ~/.bashrc
检查:pip install ohos-build
三.关于移植
1.异构通讯(重点Ability):
基于UDP的coap协议,OpenHarmony特有分布式软总线。
软总线概念:https://developer.harmonyos.com/cn/docs/documentation/doc-guides/harmonyos-features-0000000000011907
软总线案例:https://developer.harmonyos.com/cn/docs/documentation/doc-guides/samples-0000001162414961
软总线开源的部分:https://gitee.com/openharmony/communication_dsoftbus
https://gitee.com/openharmony/communication_softbus_lite
现在所实现的软总线功能:无网实现设备互联和分布式文件检索。我们把OpenHarmony功能(特性)统称为ability,把HarmonyOS的功能叫做FA_ability(例如FA卡片等等的特色功能)
下面把目光转向功能性代码:(请自己注意Build.gn吊起相关的地址,这里不多赘述)
编程步骤:
1.创建socket;
2.设置socket属性,用函数setsockopt();
3.绑定IP地址、端口等信息到socket上,用函数bind();
4.循环接收/发送数据,用函数recvfrom&sendto;
5.关闭网络连接。
创建一个socket,无论是客户端还是服务器端都需要创建一个socket。该函数返回socket文件描述符,类似于文件描述符。socket是一个结构体,被创建在内核中。
DatagramSocket类代表一个发送和接收数据包的插座该类是遵循 UDP协议 实现的一个Socket类
开发板的IP与端口号
相关参数意义(注意要手搓的定义内容):
使用UDP的主代码官方注释:
static void UDPServerTaskWithSelect(void)
{
//服务端地址信息
struct sockaddr_in server_sock;
int sin_size = sizeof(struct sockaddr_in);
//连接Wifi
if(WifiConnect(WIFI_SSID,WIFI_PASSWORD)!=0)
{
perror("Wifi connect error!");
exit(1);
}
//in_addr_t localIpaddr = GetLocalIpaddr();
uint32_t localIpaddr = htonl(GetLocalIpaddr());
char strr[IP_LEN];
inet_ntop(AF_INET, &server_sock.sin_addr, strr, sizeof(strr));
printf("GetLocalIpaddr is %s \n",strr);
//创建socket
printf("socket begin\n");
int sock_fd=-1;
if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("socket is error\r\n");
exit(1);
}
printf("sock_fd=%d\n",sock_fd);
bzero(&server_sock, sizeof(server_sock));
server_sock.sin_family = AF_INET;
//server_sock.sin_addr.s_addr = htonl(INADDR_ANY);
inet_pton(AF_INET, _SERVER_IP_, &server_sock.sin_addr); //inet_addr("127.0.0.1");
server_sock.sin_port = htons(_PROT_);
char str[IP_LEN];
inet_ntop(AF_INET, &server_sock.sin_addr, str, sizeof(str));
printf("udp server IP_addr: %s at PORT %d\n",str,ntohs(server_sock.sin_port));
//调用bind函数绑定socket和地址
if (bind(sock_fd, (struct sockaddr *)&server_sock, sizeof(struct sockaddr)) == -1)
{
perror("bind is error\r\n");
exit(1);
}
//printf("start select\r\n");
fd_set fds;
FD_ZERO(&fds);
while(true)
{
printf("select wait...\n");
FD_SET(sock_fd,&fds);
int ret = select(sock_fd + 1, &fds, NULL, NULL, NULL);
if(ret<=0)
{
//超时等
printf("[DISCOVERY]ret:%d\n", ret);
continue;
}
//printf("select\n");
if(FD_ISSET(sock_fd,&fds))
{
//客户端地址信息
struct sockaddr_in repaddr;
memset(recvbuf,0,sizeof(recvbuf));
memset(&repaddr, 0, sizeof(struct sockaddr));
int res = recvfrom(sock_fd, recvbuf, sizeof(recvbuf)-1, 0, (struct sockaddr *)&repaddr, (socklen_t *)&sin_size);
//"exit"特殊字符串,表示客户端申请断开链接
if(strlen(recvbuf)==4 && memcmp(recvbuf,"exit",5)==0)
{
printf("remote client close,msg is %s\n",recvbuf);
sendto(sock_fd, "exit", strlen("exit") + 1, 0, (struct sockaddr *)&repaddr, (socklen_t)sin_size);
}
else
{
printf("recv msg :%s\n",recvbuf);
if (sendto(sock_fd, udpSendBuf, strlen(udpSendBuf) + 1, 0, (struct sockaddr *)&repaddr, (socklen_t)sin_size) == -1)
{
perror("send error \r\n");
}
else{ printf("send msg %s \n",udpSendBuf);}
}
osDelay(10);
FD_ZERO(&fds);
}
}
close(sock_fd);
FD_CLR(sock_fd,&fds);
sock_fd=-1;
return ;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
测试客户端的成功方法:通过UDP软件进行相关的发送与接收,并查看打印信息。因为与下文介绍的MQTTX软件使用原理差不多所以这里不多赘述。
2.MQTT:
Mqtt是用于设备与服务器通讯的一种协议,使设备可以上报订阅下发信息。需要下载此协议并存放在thirdparty(第三方库),并在头文件中吊起。
从开发板厂商官网下载实验demo进行实验。因为目前大多数厂商使用的都是OpenHarmony 1.0代码作为演示,不同的源码版本在编译规则和文件名上都会不同,所以在下载的源码中的头文件吊起等也要修改才能接入mqtt协议。
Mqtt最重要要吊起的功能文件在 /home/open/Downloads/code_v3.0LTS/OpenHarmony/third_party/pahomqtt/MQTTClient-C/src里,特别是liteOS中。
四.解决方案
1.用户角度(使用方法)
储物精灵NFC版:这是一个智慧储物系统,终端可以检索到物品的拿放入信息,包括名称以及放入时间与备注、位置等等。
使用方法:放入物品时贴上写好的NFC贴片(内容是这件物品的具体信息),然后放入与拿取货架时触碰那格的碰一碰即可。
储物精灵Pro:通过镜头对监控画面抓取后上传到云端逐帧比对,此功能在红外感应人体靠近后激活。可以在终端检索到出入库信息。
使用方法:即拿即放即可。
2.实现原理(下文的步骤会详细介绍,在这里先介绍初期设想)
关于Mqtt协议在华为云的打通(设备在线激活):使用mqttX或mqttfx。
华为云:根据提示创建并获取密钥等信息,获取ClientID等身份识别信息,然后在云端的Topic(事件主题)中自定义订阅与发布,对产品进行定义。
AppGallery Connect网站:创建并注册HarmonyOS产品,根据提示流程。
设备开发具体解析:每个设备都是一个子节点,实现了基于OpenHarmony设备的L0、L1、L2设备之间的互联互通。主控程序基于 OpenHarmony JS应用程序框架设计实现,并使用MQTT物联网通信协议接入华为云IOT平台,同时可将控制指令发送至华为云IOT平台,供云端处理。DAYU开发板(软件+硬件)具体实现为中控MQTT通信过程处于内核态驱动程序,JS应用通过发起接口调用后,进入用户态调用内核态接口的流程,并且JS应用会将所需要向云端发送的MQTT协议主题内容直接传入内核态,内核态不作数据处理和解析,直接将数据发布至云端,这样设计的目的是为了在添加设备的时候,仅需改变JS应用的数据结构,并不需要修改设备的代码,完成了解耦合。
NFC录入与记录:使用NFC扩展板录入,详细请见下方软总线设备通讯链接。
智能识别对比:识别对象的数据库,这里的识别作为单一的照片识别。vuforia 的服务器制作该 target 的识别数据库,该数据库要下载并绑定工程到target。图片由摄像头获取视频逐帧比对。
3.开发角度
OpenHarmony的Ability:碰一碰贴纸,分布式软总线。货架之间可以通过分布式软总线自发现自联,即使断网也能实现信息传输。
突发断网下的储物精灵:
NFC版:断网后可以正常使用,通过分布式软总线实现货架之间的信息传输。
Pro版:网络恢复后继续逐帧识别,把断网期间的出入库情况进行补录。
不同场景下的创新:
NFC版:仅需一块屏幕即可检索多个书架的书籍,而且非常定位准确又详细。对容易忘记书籍位置的人与图书馆是友善的。(现在图书馆的编码只能精确到哪个书架,具体位置还要通过自己慢慢找单本书位置)
Pro版:像大型库房可以全自动出入库登记与在线查看可以减少人力。
五.代码结构
1.设备侧
1.1 NFC版储物精灵:
第一步:网络连接 使设备接电后自动联网
我们会在代码中预置热点名称与密码
在创建包后新建mqtt_entry.c用于存放热点自连代码的地址:
/home/open/Downloads/code-v3.0-LTS/OpenHarmony/applications/sample/wifi-iot/app/mqtt_demo
这里把原有的ability等量代换成了自发现热点。
*OpenHarmony_ability的碰一碰自发现与自配网见下述。
第二步:上报订阅与下发 在此包内创建main函数(同时声明,创建.h)
包的地址:
/home/open/Downloads/code-v3.0-LTS/OpenHarmony/applications/sample/wifi-iot/app/mqtt_demo
第五步:储物精灵保险模式&舵机开门
舵机开锁:

保险模式:
第六步:标注GPIO口
识别GPIO口与接入(这里要注意一个接的是正极一个是接地还有一个为信号传输口)
第七步:概括吊起mqtt协议(build.gn版)
与主函数平行的Build.gn,吊起函数与第三方库的内容:
Build.gn:与APP并列的build.gn用于指出要编译的主函数,可以使用startup后面跟要编译的主包也可以直接features进行选中,在这里可以把不需要参与编译的项目通过#给注释掉。
在start_up里的builld.gn:
Pro版储物精灵:(使用第三方平台:Vuforia)
首先我们的原理就是上传画面到云端,然后逐帧分解比对。
由于自己手搓数据库需要进行大规模实验,所以我们使用第三方的Vuforia接入并比对。
官网介绍文档:https://library.vuforia.com/platform-support/external-camera
2.软件侧(偏向软件,但是还是嵌入式开发)
2.1 软件制作(关于分布式软总线)
官网案例:https://developer.harmonyos.com/cn/documentation/Samples/
更多内容请参考上述的基于UDP的coap协议使用教程,这里不再赘述。
2.2 NFC功能
官方开发文档:https://gitee.com/openharmony-sig/knowledge_demo_smart_home/blob/master/dev/docs/NFC_label_definition/README.md

步骤一:接收服务器的存储代码
步骤二:射频贴纸&复旦卡拉取本地方案(实验中,将会在下一次更新全量代码)
写入复旦卡请用第三方的软件,NFC射频贴纸使用应用调试助手。应用调试助手的使用方法和注册方法等请看上下文的介绍以及附带的官网开发文档链接。

步骤三:智能窗帘轨解决方案
因为马达可以无限前后方向的对窗帘轨进行拉动所以选择马达来进行拉动。另外要注意马达的功率来判断是否加入继电器,详情请移步源码仓(购买马达时要向供应商索要相关数据,同时向开发板供应商索要已公开的脚位置图)
static void RtcTimeUpdate(void)
{
extern int SntpGetRtcTime(int localTimeZone, struct tm *rtcTime);
struct tm rtcTime;
SntpGetRtcTime(CONFIG_LOCAL_TIMEZONE,&rtcTime);
RaiseLog(LOG_LEVEL_INFO, "Year:%d Month:%d Mday:%d Wday:%d Hour:%d Min:%d Sec:%d", \
rtcTime.tm_year + BASE_YEAR_OF_TIME_CALC, rtcTime.tm_mon + 1, rtcTime.tm_mday,\
rtcTime.tm_wday, rtcTime.tm_hour, rtcTime.tm_min, rtcTime.tm_sec);
if (rtcTime.tm_wday > 0) {
g_appController.curDay = rtcTime.tm_wday - 1;
} else {
g_appController.curDay = EN_SUNDAY;
}
g_appController.curSecondsInDay = rtcTime.tm_hour * CN_SECONDS_IN_HOUR + \
rtcTime.tm_min * CN_SECONDS_IN_MINUTE + rtcTime.tm_sec + 8; // add 8 ms offset
}
static uint32_t Time2Tick(uint32_t ms)
{
uint64_t ret;
ret = ((uint64_t)ms * osKernelGetTickFreq()) / CN_MINISECONDS_IN_SECOND;
return (uint32_t)ret;
}
#define CN_REACTION_TIME_SECONDS 1
static void BoardLedButtonCallbackF1(char *arg)
{
static uint32_t lastSec = 0;
uint32_t curSec = 0;
RaiseLog(LOG_LEVEL_INFO, "BUTTON PRESSED");
curSec = g_appController.curSecondsInDay;
if((curSec) < (lastSec + CN_REACTION_TIME_SECONDS)) {
RaiseLog(LOG_LEVEL_WARN, "Frequecy Pressed Button");
return;
}
lastSec = curSec;
g_appController.curtainStatus = CN_BOARD_SWITCH_ON;
g_appController.pwmLedDutyCycle = \
g_appController.pwmLedDutyCycle > 0 ? g_appController.pwmLedDutyCycle:CONFIG_LED_DUTYCYCLEDEFAULT;
osEventFlagsSet(g_appController.curtainEvent, CN_LAMP_EVENT_SETSTATUS);
return;
}
static void BoardLedButtonCallbackF2(char *arg)
{
uint32_t lastSec = 0;
uint32_t curSec = 0;
RaiseLog(LOG_LEVEL_INFO, "BUTTON PRESSED");
curSec = g_appController.curSecondsInDay;
if ((curSec) < (lastSec + CN_REACTION_TIME_SECONDS)) {
RaiseLog(LOG_LEVEL_WARN, "Frequecy Pressed Button");
return;
}
lastSec = curSec;
g_appController.curtainStatus = CN_BOARD_SWITCH_OFF;
osEventFlagsSet(g_appController.curtainEvent, CN_LAMP_EVENT_SETSTATUS);
return;
}
#define CURTAIN_MOTOR_GPIO_IDX 8
#define WIFI_IOT_IO_FUNC_GPIO_8_GPIO 0
#define WIFI_IOT_IO_FUNC_GPIO_14_GPIO 4
#define MOTOR_WORK_SECOND 6
static void E53SC1_MotorInit(void)
{
IoTGpioInit(CURTAIN_MOTOR_GPIO_IDX);
IoTGpioSetFunc(CURTAIN_MOTOR_GPIO_IDX, WIFI_IOT_IO_FUNC_GPIO_8_GPIO);
IoTGpioSetDir(CURTAIN_MOTOR_GPIO_IDX, IOT_GPIO_DIR_OUT); //设置GPIO_8为输出模式
return;
}
static void E53SC1_SetCurtainStatus(int curtainStatus)
{
if ((curtainStatus == CN_BOARD_CURTAIN_OPEN) || (curtainStatus == CN_BOARD_CURTAIN_CLOSE)) {
IoTGpioSetOutputVal(CURTAIN_MOTOR_GPIO_IDX, 1); //设置GPIO_8输出高电平打开电机
sleep(MOTOR_WORK_SECOND);
IoTGpioSetOutputVal(CURTAIN_MOTOR_GPIO_IDX, 0); //设置GPIO_8输出低电平关闭电机
}
return;
}
static void DataCollectAndReport(const void *arg)
{
(void)arg;
uint32_t curtainEvent;
uint32_t waitTicks;
waitTicks = Time2Tick(CONFIG_SENSOR_SAMPLE_CYCLE);
while (1) {
curtainEvent = osEventFlagsWait(g_appController.curtainEvent, CN_CURTAIN_EVENT_SETSTATUS, \
osFlagsWaitAny, waitTicks);
if (curtainEvent & CN_CURTAIN_EVENT_SETSTATUS) {
RaiseLog(LOG_LEVEL_INFO, "GetEvent:%08x", curtainEvent);
E53SC1_SetCurtainStatus(g_appController.curtainStatus);
}
(void) IotProfile_Report(g_appController.curtainStatus);
}
return;
}
static int UpdateShedule(CommandParamSetShedule *shedule)
{
if (shedule->num == 1 && shedule->day[0] == 0) { // set the one time schedule to current weekday
shedule->day[0] = (g_appController.curDay + 1);
}
switch (shedule->option) {
case 'A':
IOT_ScheduleAdd(shedule->scheduleID, shedule->day, shedule->num, shedule->startHour * CN_SECONDS_IN_HOUR +\
shedule->startMinute * CN_SECONDS_IN_MINUTE, shedule->duration, shedule->shedulecmd.cmd, 0);
break;
case 'U':
IOT_ScheduleUpdate(shedule->scheduleID, shedule->day, shedule->num, shedule->startHour * CN_SECONDS_IN_HOUR +\
shedule->startMinute * CN_SECONDS_IN_MINUTE, shedule->duration, shedule->shedulecmd.cmd, 0);
break;
case 'D':
IOT_ScheduleDelete(shedule->scheduleID, shedule->day, shedule->num, shedule->startHour * CN_SECONDS_IN_HOUR +\
shedule->startMinute * CN_SECONDS_IN_MINUTE, shedule->duration, shedule->shedulecmd.cmd, 0);
break;
default:
RaiseLog(LOG_LEVEL_ERR, "the schedule has no such option!\n");
break;
}
return 0;
}
void CurrentTimeCalcTimerHander(){
g_appController.curSecondsInDay ++;
}
#define TimeCalcTicks_NUMBER 100
#define CN_MINISECONDS_IN_1000MS 1000
static void CurtainShedule(void)
{
int startSecondInDay = 0;
int endSecondInDay = 0;
int settingCmd = 0;
int executeTaskTime = 0; // indicate the do something busy
osTimerId_t CurrentTimeCalc_id;
CurrentTimeCalc_id = osTimerNew(CurrentTimeCalcTimerHander, osTimerPeriodic, NULL, NULL);
osTimerStart(CurrentTimeCalc_id, TimeCalcTicks_NUMBER);
while (1) {
osDelay(Time2Tick(CN_MINISECONDS_IN_1000MS));
if (g_appController.curSecondsInDay >= CN_SECONS_IN_DAY) {
g_appController.curSecondsInDay = 0;
g_appController.curDay++;
if (g_appController.curDay >= EN_DAYALL) {
g_appController.curDay = EN_MONDAY;
}
IOT_ScheduleSetUpdate(1);
}
// check if we need do some task here
if (IOT_ScheduleIsUpdate(g_appController.curDay, g_appController.curSecondsInDay)) {
if (executeTaskTime > 0) {
executeTaskTime = 0;
if (g_appController.curtainStatus == CN_BOARD_CURTAIN_OPEN) {
g_appController.curtainStatus = CN_BOARD_CURTAIN_CLOSE;
osEventFlagsSet(g_appController.curtainEvent, CN_CURTAIN_EVENT_SETSTATUS);
}
}
startSecondInDay = IOT_ScheduleGetStartTime();
endSecondInDay = startSecondInDay + IOT_ScheduleGetDurationTime();
IOT_ScheduleGetCommand(&settingCmd, NULL);
}
RaiseLog(LOG_LEVEL_INFO, "start:%d end:%d cur:%d",startSecondInDay, endSecondInDay, g_appController.curSecondsInDay);
if ((endSecondInDay == startSecondInDay) && (g_appController.curSecondsInDay == endSecondInDay)) {
if (g_appController.curtainStatus != settingCmd) {
RaiseLog(LOG_LEVEL_INFO, "Triggering");
g_appController.curtainStatus = settingCmd;
osEventFlagsSet(g_appController.curtainEvent, CN_CURTAIN_EVENT_SETSTATUS);
}
IOT_ScheduleSetUpdate(1);
}
}
return;
}
int IotProfile_CommandCallback(int command, void *buf)
{
CommandParamSetShedule setSheduleParam;
CommandParamSetCurtain setCurtainParam;
//CommandParamSetDutyCycle setDutyCycleParam;
CLOUD_CommandType cmd = (CLOUD_CommandType)command;
if (cmd == CLOUD_COMMAND_SETCURTAIN_STATUS) {
setCurtainParam = *(CommandParamSetCurtain *)buf;
g_appController.curtainStatus = setCurtainParam.status;
RaiseLog(LOG_LEVEL_INFO, "setCurtainParam.status:%d\r\n", setCurtainParam.status);
osEventFlagsSet(g_appController.curtainEvent, CN_LAMP_EVENT_SETSTATUS);
return 0;
} else if (cmd == CLOUD_COMMAND_SETSHEDULE) {
setSheduleParam = *(CommandParamSetShedule *)buf;
RaiseLog(LOG_LEVEL_INFO, "setshedule:day:%d hour:%d minute:%d duration:%d \r\n", \
setSheduleParam.day,setSheduleParam.startHour,setSheduleParam.startMinute, setSheduleParam.duration);
return UpdateShedule(&setSheduleParam);
}
return -1;
}
static int IotWifiInfo_get(char *ssid, int id_size, char *pwd, int pd_size)
{
int retval = UtilsGetValue(SID_KEY, ssid, id_size);
if (retval <= 0) {
RaiseLog(LOG_LEVEL_ERR, "no such ssid stored! \n");
return 0;
}
if ( UtilsGetValue(PWD_KEY, pwd, pd_size) < 0) {
RaiseLog(LOG_LEVEL_INFO, "ssid(%s) no password stored! \n", ssid);
} else {
RaiseLog(LOG_LEVEL_INFO, "ssid : %s, pwd : %s! \n", ssid, pwd);
}
return 1;
}
static void IotWifiInfo_set(char *ssid, char *pwd)
{
if (UtilsSetValue(SID_KEY, ssid) != 0) {
RaiseLog(LOG_LEVEL_ERR, "store ssid failed! \n");
return;
}
if (UtilsSetValue(PWD_KEY, pwd) != 0) {
RaiseLog(LOG_LEVEL_ERR, "store password failed! \n");
UtilsDeleteValue(SID_KEY);
return;
}
RaiseLog(LOG_LEVEL_INFO, "store password success! \n");
}
static void IotMainTaskEntry(const void *arg)
{
osThreadAttr_t attr;
NfcInfo nfcInfo;
(void)arg;
char ssid[BUFF_SIZE] = {0};
char pwd[BUFF_SIZE] = {0};
int ret = 0;
g_appController.pwmLedDutyCycle = CONFIG_LED_DUTYCYCLEDEFAULT;
BOARD_InitPwmLed();
BOARD_InitWifi();
E53SC1_MotorInit();
IOT_ScheduleInit();
ret = Board_IsButtonPressedF2();
osDelay(MAIN_TASK_DELAY_TICKS);
LedFlashFrequencySet(CONFIG_FLASHLED_FRENETCONFIG);
nfcInfo.deviceID = "6136ceba0ad1ed02866fa3b2_Curtain01";
nfcInfo.devicePWD = "12345678";
if (ret) {
RaiseLog(LOG_LEVEL_INFO, "Netconfig Button has pressed! \n");
if (BOARD_NAN_NetCfgStartConfig(SOFTAP_NAME, ssid, sizeof(ssid), pwd, sizeof(pwd)) < 0) {
RaiseLog(LOG_LEVEL_ERR, "BOARD_NetCfgStartConfig failed! \n");
return;
} else {
ret = AFTER_NETCFG_ACTION;
}
} else {
ret = IotWifiInfo_get(ssid, sizeof(ssid), pwd, sizeof(pwd));
if (ret == 0) {
if (BOARD_NAN_NetCfgStartConfig(SOFTAP_NAME, ssid, sizeof(ssid), pwd, sizeof(pwd)) < 0) {
RaiseLog(LOG_LEVEL_ERR, "BOARD_NetCfgStartConfig failed! \n");
return;
} else {
ret = AFTER_NETCFG_ACTION;
}
}
}
LedFlashFrequencySet(CONFIG_FLASHLED_FREWIFI);
if (BOARD_ConnectWifi(ssid, pwd) != 0) {
RaiseLog(LOG_LEVEL_ERR, "BOARD_ConnectWifi failed! \n");
if (ret == AFTER_NETCFG_ACTION) {
NotifyNetCfgResult(NETCFG_DEV_INFO_INVALID);
}
hi_hard_reboot(HI_SYS_REBOOT_CAUSE_CMD);
return;
}
if (ret == AFTER_NETCFG_ACTION) {
RaiseLog(LOG_LEVEL_DEBUG, "Connect wifi success ! \n");
NotifyNetCfgResult(NETCFG_OK);
osDelay(MAIN_TASK_DELAY_TICKS);
RaiseLog(LOG_LEVEL_DEBUG, "StopNetCfg wifi success ! \n");
StopNetCfg();
IotWifiInfo_set(ssid, pwd);
}
LedFlashFrequencySet(CONFIG_FLASHLED_FRECLOUD);
RtcTimeUpdate();
if (CLOUD_Init() != 0) {
return;
}
if (CLOUD_Connect(nfcInfo.deviceID, nfcInfo.devicePWD, \
CONFIG_CLOUD_DEFAULT_SERVERIP, CONFIG_CLOUD_DEFAULT_SERVERPORT) != 0) {
return;
}
LedFlashFrequencySet(CONFIG_FLASHLED_WORKSWELL);
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = CONFIG_TASK_DEFAULT_STACKSIZE;
attr.priority = CONFIG_TASK_DEFAULT_PRIOR;
attr.name = "DataCollectAndReport";
if (osThreadNew((osThreadFunc_t)DataCollectAndReport, NULL, (const osThreadAttr_t *)&attr) == NULL) {
return;
}
attr.name = "CurtainShedule";
if (osThreadNew((osThreadFunc_t)CurtainShedule, NULL, (const osThreadAttr_t *)&attr) == NULL) {
return;
}
return;
}
static void IotMainEntry(void)
{
osThreadAttr_t attr;
RaiseLog(LOG_LEVEL_INFO, "DATA:%s Time:%s \r\n",__FUNCTION__, __DATE__, __TIME__);
g_appController.curtainEvent = osEventFlagsNew(NULL);
if ( g_appController.curtainEvent == NULL) {
return;
}
// Create the IoT Main task
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = CONFIG_TASK_MAIN_STACKSIZE;
attr.priority = CONFIG_TASK_MAIN_PRIOR;
attr.name = "IoTMain";
(void) osThreadNew((osThreadFunc_t)IotMainTaskEntry, NULL, (const osThreadAttr_t *)&attr);
return;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
- 224.
- 225.
- 226.
- 227.
- 228.
- 229.
- 230.
- 231.
- 232.
- 233.
- 234.
- 235.
- 236.
- 237.
- 238.
- 239.
- 240.
- 241.
- 242.
- 243.
- 244.
- 245.
- 246.
- 247.
- 248.
- 249.
- 250.
- 251.
- 252.
- 253.
- 254.
- 255.
- 256.
- 257.
- 258.
- 259.
- 260.
- 261.
- 262.
- 263.
- 264.
- 265.
- 266.
- 267.
- 268.
- 269.
- 270.
- 271.
- 272.
- 273.
- 274.
- 275.
- 276.
- 277.
- 278.
- 279.
- 280.
- 281.
- 282.
- 283.
- 284.
- 285.
- 286.
- 287.
- 288.
- 289.
- 290.
- 291.
- 292.
- 293.
- 294.
- 295.
- 296.
- 297.
- 298.
- 299.
- 300.
- 301.
- 302.
- 303.
- 304.
- 305.
- 306.
- 307.
- 308.
- 309.
- 310.
- 311.
- 312.
- 313.
- 314.
- 315.
- 316.
- 317.
- 318.
- 319.
- 320.
- 321.
- 322.
- 323.
- 324.
- 325.
- 326.
- 327.
- 328.
- 329.
- 330.
- 331.
- 332.
- 333.
- 334.
- 335.
- 336.
- 337.
- 338.
- 339.
- 340.
- 341.
- 342.
- 343.
- 344.
- 345.
- 346.
- 347.
- 348.
- 349.
- 350.
- 351.
- 352.
- 353.
- 354.
- 355.
- 356.
- 357.
- 358.
- 359.
- 360.
- 361.
- 362.
- 363.
步骤四:无感配网解决方案(重要ability)
OpenHarmony设备之间的信息传递利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家与设备之间的互联。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规SoftAP配网方式共存。
详细的开发流程请移步官网介绍:https://gitee.com/openharmony-sig/knowledge_demo_smart_home/tree/master/dev/docs/NFC_label_definition
数字管家NFC生成器:https://pan.baidu.com/s/1H1oSLKOPODxySPA8aMA3Tg?pwd=enq9
以上使用方法请查看team_X中的相关使用介绍,这里不再赘述。
2.2 第三方平台接入
首先我们的原理就是上传画面到云端,然后逐帧分解比对。由于自己手搓数据库需要进行大规模实验,所以我们使用第三方的Vuforia接入并比对。我们来看一下关键部分:

官网介绍文档:https://library.vuforia.com/platform-support/external-camera
应用开发教程:https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ability-page-switching-0000000000037999
由于移植问题所以官网接入仅提供了安卓的SDK所以软件我们目前智能实现自动打开网页和填入有关信息比对后再复制粘贴出相关的结果,请见谅。
现在在手搓pytorch训练出入库识别,因为没有相关的数据集在飞桨中发现。不过把数据集怼到设备里不是问题,数据集是分场景但是不分对口的操作系统的。这项功能将会在下一次的更新中完成,敬请期待。
2.3 实时摄像功能与智能检测光照值功能(正在实验中,将会在下一次更新全量代码)
官网提供的完整摄像接入方法:https://gitee.com/openharmony-sig/knowledge_demo_smart_home/blob/master/dev/docs/smart_door_viewer_3516/README.md
未发行的明暗检测部分源码:
2.4 分布式检索功能(实验中)
传统的分布式使用的是Elasticsearch进行,鉴于OpenHarmony能力所以需要开发出对口的ability。
开发说明(包括OH分布式文件):
-
构造分布式数据库管理类(创建 KvManagerConfig 对象)
-
获取/创建单版本分布式数据库(声明需要创建的单版本分布式数据库ID说明)
-
订阅分布式数据更改(客户端需要实现KvStoreObserver接口&结构并注册KvStoreObserver实例)
-
构造需要写入单版本分布式数据库的Key和Value(将键值数据写入单版本分布式数据库)
-
构造需要从单版本分布式数据库快照中查询的Key(数据取自单版本分布式数据库快照)
-
获取设备列表与同步数据(PUSH_ONLY)
-
首先get到设备数据交换权限
-
然后get到分布式的文件目录
-
关于API的开放能力请详见官方文档,这里不再赘述。
-
怼相关接口(正在实验的内容)
-
设置搜索属性与插入索引和重构查询等将会在成品完成后(下一次提交中)进行补充,敬请期待!
六.必用帮助
1.Beyond Conpare
Beyond Conpare是一款优秀的文件对比软件

进入软件后通过点击新建会话-文件夹比较即可,下图为我们通过SSH建立的成功连接后选中的文件夹。(在这里通过输入IP地址后本地ssh后登录即可得到)

下图为选中自己所需要比对的文件夹进行比对

选中左侧文件后右键点击对比再选中右边文件即可进行比较。

可以用于比对底层的更改与自己代码上的变动,从而快速的定位找到问题进行修改。

上图是两份文件对比后的结果。当然,在beyond的这个界面上双击选中文件还可以更加详细的查看文件内容等,此外此软件还可以对文本文档直接进行更改,包括修改内容与删除文件等等。
2.FinalShell
首先是Ubuntu端:建立远程访问(SSH)
我们可以使用finalshell的本地ssh对ubuntu虚拟机进行文件拖拽与远程检查虚拟机中的文件。首先我们要通过linux的ssh登录。
安装与开启Ubuntu的ssh需要在终端命令框键如下命令
sudo apt-get update
sudo apt-get install openssh-client=1:8.2p1-4
sudo apt-get install openssh-server
sudo systemctl start ssh
在终端命令框中输入ifconfig对虚拟机的IP进行检查,在ens33的inet后的就是。

然后我们把目光转向finalshell的远程登录界面:

建立SSH连接的端口选为22(默认),然后键入IP与用户名密码等等再点击确定。

在这里可以看到虚拟机的硬件实时信息,就像windows下的任务管理器一样。

选中文件右键后即可上传与下载或删除与新建文件。一般从虚拟机拉取后下载到本地的文件都会下载到桌面的finalshell这个文件夹内(为下载时系统自建),到此远程调用结束。
七.云端打通
1.注册与获取
1.1 注册
注册华为云并登录,并在华为云官方获取Client ID等身份识别信息,然后在云端的Topic(事件主题)中自定义订阅与发布。对产品进行定义。
配置官方教程:https://support.huaweicloud.com/qs-iothub/iot_05_00122.html

上图为自定义topic
1.2 获取
首先去华为云平台创建产品与定义模型(获取IP与端口),然后获取上报和下发的通信数据,随后云平台就会给出设备ID与密钥。
2.云端激活
2.1 激活步骤
首先去华为云平台创建产品与定义模型(获取IP与端口),然后获取上报和下发的通信数据,随后云平台就会给出设备ID与密钥。(后面会详细介绍)。我们可以查看到上报的消息与进行命令的下发:

2.2 Mqttx
关于Mqtt协议在华为云的打通(设备在线激活):使用mqttX或mqttfx。
华为云:根据提示创建并获取密钥等信息,获取ClientID等身份识别信息,然后在云端的Topic(事件主题)中自定义订阅与发布,对产品进行定义。
注意:初次激活云端在线设备可以通过MqttX或MqttFX进行:上报,订阅,下发的实验,开发者也可以通过这个进行原理的理解。

这里使用的是MqttX软件,上面显示已成功。

云端显示设备在线(MQTT成功打通)
2.3 AppGallery Connect网站:创建并注册HarmonyOS产品,根据提示流程。
八. 编译烧录
1.编译方法
1.1 命令行编译(仅展示一种编译,因为开发的子节点有很多这里就不逐个展示了)
在Hb环境配置好之后所用到的命令:
注意:在hb set后可通过上下键选择要编译的文件然后回车,选中后即build。

选中此文件夹后进行编译(hb build)

上图则是编译成功后的显示的最后两行。
1.2 VS code编译
作为一款华为集成的硬件编程插件,里面是有编译与烧录功能的。如果要进行编译首先要在PROJECT TASKS中点击build,同时在编译过程中在源码里会产生config.json文件。在源码修改之后需要点击clean去清除config.json,然后才能继续点击Build进行编译。编译成功后点击Upload烧录到开发板中。

使用VS code成功编译的提示
2.镜像获取
2.1 嵌入式设备镜像
在源码中的out文件夹中,和使用ssh进行拉取。拉取到windows下后使用Hiburn进行烧录,波特率设置为2000000为最佳。
2.2 OpenHarmony发行版镜像

下载最新的镜像包之后,我们可以得到如下文件。

使用官网下载的烧录工具,根据介绍把镜像地址输入到烧录软件中。
3.烧录方法
3.1 HIburn
在build成功后开发者就会发现在源码中的out文件夹中看到allinone.bin,然后使用finalshell或MobaXterm的ssh发送到windows下使用Hiburn进行烧录即可(波特率 最大3000000,否则会烧坏板子)。
在软件点击烧录之后点击开发板的复位按钮才会开始烧录。

HiBurn的配置方法,点击Connect即可烧录
3.2 RKDevTool

烧录时按住板子的重启键和掀开屏幕后发现的按键(这两个按键需要同时按住)
即可烧录完成。
九.查漏补缺
1. 关于虚拟机的安装
建议安装Ubuntu操作系统,以满足OpenHarmony的编程环境要求。
2.关于消歧义
Lite-m:适用于微型设备。
Lite-a:适用于标准设备。
DAYU_OS:润和公司打造的Linux内核商业发行版,可实现OH应用开发。
3.关于数字管家
这个项目中没有用到但是做了相关实验(简单介绍一下)
首先我们手机的API版本要在API 6以上才可以进行数字管家开发。

APPGallery Connect 的数字管家需要通过在APPGallery Connect中创建项目后添加应用从而获取Json文件,在完成下述的2后把此文件放在码云中下载的FA源码的:DistSchedule\netconfig\src\main\resources中。然后按照文档开发UI界面,点击构建的Generate Key and CSR创建用户名与密钥进行签名。
官网在我的项目中创建项目,选择HarmonyOS平台等完成填写:
https://developer.huawei.com/consumer/cn/service/josp/agc/index.html#/
处理逻辑:
用户操作界面:在slice目录下新建 xxxSlice.java文件,通过addActionRoute方法为此AbilitySlice配置一条路由规则,并且在在应用配置文件(config.json)中注册。在resources/base/layout下新建对应xml布局文件,在上述两个文件中编写相应的UI。
数字管家数据处理:从slice获取deviceId:在onStart中通过调用DeviceID等,获取设备的名称等方便数字管家识别设备。从slice页面获取状态:开关锁可以直接调用intent.getBooleanParam来确定是进行开关锁还是对门锁的日程进行编排。
配置设备端信息:在DeviceData的initData中,根据设备ProductID添加设备图片ID、跳转的action参数和解析方法,配置完成后设备列表页、用户页面等都能通过该配置进行图片加载、路由跳转和解析。
在entry\src\main\js\default\pages\index为我们主要修改的内容。
页面:(hml)

还要看一下手机应用侧的netconfig配网模块
事件:(js)
或者是用另一种方法:把Fa文档中和你产品对口的smart源码拷贝到team_X中。
最后开发完在数字管家开发完成之后我们要对文件进行签名即可:

Alias:密钥的名称信息,用于签名的配置。
Password:密钥的密码(系统自动填入)
Certificate:证书的具体信息 名称 组织与国家代码等等。

如上图所示我们点击new然后新建密钥包,并且输入密码等信息进行生成
注意:这里的密码必须由大小写和数字与符号组成以保证安全性。
另外一种签名方式:

应用调试助手:通过华为应用市场下载应用调试助手,点击并申请Product ID并选择。
在根据APPGallery Connect的指示完成页面填写后手机贴近NFC扩展板或NFC射频贴纸点击<置入缓存区即可>

下面为官方的规则文档:
OpenHarmony应用接口规则:
https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/Readme-CN.md
基于JS的页面开发规则:
https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-js/Readme-CN.md#/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-js/js-components-common-attributes.md

NFC写入:
- 最后进行接口对接与NFC写入就可以了(通过应用调试助手写入NFC识别详细用于快速让手机识别到设备从而吊起数字管家实现鸿蒙的Ability)
- 可以写到开发板的NFC预存区,也可以写在huawei share的碰一碰卡片上。(目前这两种写法都可以写无数次,在下一次写入时会自动清除上一次所写的)
非常详细的实践过程,必须支持下!
太强了,必须收藏学习
是studio的吧,你那儿写成“studo”了……