基于openharmony的储物精灵
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于openharmony的储物精灵相关的知识,希望对你有一定的参考价值。
基于openharmony的储物精灵
一.项目简介
1.产品描述
基于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
复制粘贴到.bashrc的最后一行:export PATH=~/.local/bin:$PATH
更新变量:source ~/.bashrc 检查:pip install ohos-build
三.关于移植
1.异构通讯(重点Ability):
基于UDP的coap协议,鸿蒙特有分布式软总线。
软总线案例: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吊起相关的地址,这里不多赘述)
UDP接口:vendor\\hisi\\hi3861\\hi3861\\third_party\\lwip_sack\\include\\sockets.h
编程步骤:1.创建socket;2.设置socket属性,用函数setsockopt(); 3.绑定IP地址、端口等信息到socket上,用函数bind(); 4.循环接收/发送数据,用函数recvfrom&sendto; 5.关闭网络连接。
创建一个socket,无论是客户端还是服务器端都需要创建一个socket。该函数返回socket文件描述符,类似于文件描述符。socket是一个结构体,被创建在内核中。
class UdpClient
private DatagramSocket client;
public String sendAndReceive(String ip, int port, String msg)
String responseMsg = "";
try
//Create a client-side DatagramSocket object without having to pass in addresses and objects
client = new DatagramSocket();
byte[] sendBytes = msg.getBytes();
//Encapsulates the address of the destination to be sent
InetAddress address = InetAddress.getByName(ip);
//Encapsulates the object to send the DatagramPacket
DatagramPacket sendPacket = new DatagramPacket(sendBytes,sendBytes.length,address,port);
try
//sent Data
client.send(sendPacket);
catch (Exception e)
// e.printStackTrace();
byte[] responseBytes = new byte[2048];
//Create a DatagramPacket object for the response information
DatagramPacket responsePacket = new DatagramPacket(responseBytes,responseBytes.length);
try
//Waiting for the response information, as on the server side, the client blocks at this step until it receives a packet
client.receive(responsePacket);
catch (Exception e)
// e.printStackTrace();
//Parse the packet contents
responseMsg = new String(responsePacket.getData(),0,responsePacket.getLength());
catch (Exception e)
// e.printStackTrace();
finally
//Close the client
if(client != null)
client.close();
client = null;
return responseMsg;
DatagramSocket类代表一个发送和接收数据包的插座该类是遵循 UDP协议 实现的一个Socket类
#define _PROT_ 8800 //UDP server port number
#define _SERVER_IP_ "666.66.66.666"
#define TCP_BACKLOG 5
#define IP_LEN 16
#define WIFI_SSID "rui666" //WiFi name
#define WIFI_PASSWORD "1145141919810" //WIFI oassword
开发板的IP与端口号
public void performClassification()
int res = classifier.getResult(accelMeasurements, gyroMeasurements);
TaskDispatcher uiTaskDispatcher = this.getContext().getUITaskDispatcher();
String lab = classes[res];
result = lab;
TaskDispatcher globalTaskDispatcher = getContext().getGlobalTaskDispatcher(TaskPriority.DEFAULT);
globalTaskDispatcher.asyncDispatch(new Runnable()
public void run()
HiLog.warn(label, udpClient.sendAndReceive("666.66.66.666", 8800, result));
);
相关参数意义(注意要手搓的定义内容):
sin_family //Refers to protocol families, which can only be AF_INET in socket programming
sin_port //Storage port number (using network byte order)
sin_addr //Store the IP address and use the in_addr this data structure
sin_zero //Empty bytes are reserved in order to keep sockaddr and sockaddr_in two data structures the same size
fd //socket
buf //UDP datagram buffer (contains data to be sent)
len //The length of the UDP datagram
flags //Invocation operation mode (typically set to 0)
addr //A struct that points to the host address information that receives the data (type conversion required sockaddr_in)
alen //The length of the structure referred to by addr
nfds //Represents the range of all file descriptors in a collection
readfds //Select monitors a collection of readable file handles、
writefds //A collection of writable file handles that select monitors
exceptfds //A collection of exception file handles that select monitors
timeout //The timeout end time of select() this time, NULL means permanent wait
使用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 ;
测试客户端的成功方法:通过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.开发角度
鸿蒙的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
int ret;
errno_t rc;
hi_wifi_assoc_request assoc_req = 0;
/* 拷贝SSID到assoc的req */
/* copy SSID to assoc_req */
rc = memcpy_s(assoc_req.ssid, HI_WIFI_MAX_SSID_LEN + 1, "rui666", 8); //热点名
/* WPA-PSK. CNcomment: 认证类型:WPA2-PSK.CNend */
if (rc != EOK)
return -1;
//热点加密方式
assoc_req.auth = HI_WIFI_SECURITY_WPA2PSK;
memcpy(assoc_req.key, "88888888", 11); //热点的密码
ret = hi_wifi_sta_connect(&assoc_req);
if (ret != HISI_OK)
return -1;
return 0;
//预置热点名和密码 在设备通电后会自连
这里把原有的ability等量代换成了自发现热点。 *鸿蒙ability的碰一碰自发现与自配网见下述。
第二步:上报订阅与下发 在此包内创建main函数(同时声明,创建.h)
包的地址: /home/open/Downloads/code-v3.0-LTS/OpenHarmony/applications/sample/wifi-iot/app/mqtt_demo
void mqtt_callback(MessageData *msg_data)
size_t res_len = 0;
uint8_t *response_buf = NULL;
char topicname[45] = "$crsp/" ;
LOS_ASSERT(msg_data);
printf("topic %.*s receive a message\\r\\n", msg_data->topicName->lenstring.len, msg_data->topicName->lenstring.data);
printf("message is %.*s\\r\\n",
msg_data->message->payloadlen,
msg_data->message->payload);
int mqtt_connect(void)
int rc = 0;
NetworkInit(&n);
NetworkConnect(&n, "a161fa3144.iot-mqtts.cn-north-4.myhuaweicloud.com", 1883);
buf_size = 4096+1024;
onenet_mqtt_buf = (unsigned char *) malloc(buf_size);
onenet_mqtt_readbuf = (unsigned char *) malloc(buf_size);
if (!(onenet_mqtt_buf && onenet_mqtt_readbuf))
printf("No memory for MQTT client buffer!");
return -2;
MQTTClientInit(&mq_client, &n, 1000, onenet_mqtt_buf, buf_size, onenet_mqtt_readbuf, buf_size);
MQTTStartTask(&mq_client);
data.keepAliveInterval = 30;
data.cleansession = 1;
data.clientID.cstring = "61f6e729de9933029be57672_88888888_0_0_2022020905";
data.username.cstring = "61f6e729de9933029be57672_88888888";
data.password.cstring = "43872acc0b1e6aa7bf9e6a69f12aa9b1ebc07daffb67e18cf905c847a594f813";
data.cleansession = 1;
mq_client.defaultMessageHandler = mqtt_callback;
//连接服务器
rc = MQTTConnect(&mq_client, &data);
//订阅消息,设置回调函数
MQTTSubscribe(&mq_client, "porsche", 0, mqtt_callback);
while(1)
MQTTMessage message;
message.qos = QOS1;
message.retained = 0;
message.payload = (void *)"openharmony";
message.payloadlen = strlen("openharmony");
//上报
if (MQTTPublish(&mq_client, "hi3861", &message) < 0)
printf("MQTTPublish faild !\\r\\n");
IoTGpiosetOutputVal(9, 0); //9gpio 0 light on
usleep(1000000);
return 0;
第五步:储物精灵保险模式&舵机开门
舵机开锁:
int servoID =0;
void My_servo(uint8_t servoID,int angle)
int j=0;
int k=2000/200;
angle = k*angle;
for (j=0;j<5;j++)
IoTGpioSetOutputVal(servoID, 1);
hi_udelay(angle);
IoTGpioSetOutputVal(servoID, 1);
hi_udelay(20000-angle);
保险模式:
static int DealSetPassword(cJSON *objCmd)
int ret = -1;
char *pstr = NULL;
cJSON *objParas = NULL;
cJSON *objPara = NULL;
CommandParamSetPsk setLockPskParam;
memset(&setLockPskParam, 0x00, sizeof(CommandParamSetPsk));
if ((objParas = cJSON_GetObjectItem(objCmd, "paras")) == NULL)
RaiseLog(LOG_LEVEL_ERR, "Paras not exist");
return ret;
if ((objPara = cJSON_GetObjectItem(objParas, "PskId")) != NULL)
char *id = cJSON_GetStringValue(objPara); //密码标识(string型)
if (id == NULL || strlen(id) > LOCK_ID_LENGTH)
RaiseLog(LOG_LEVEL_ERR, "check lock id failed!");
return -1;
strncpy(setLockPskParam.id, id, strlen(id));
else
return ret;
if ((objPara = cJSON_GetObjectItem(objParas, "Option")) != NULL)
char *option = cJSON_GetStringValue(objPara);
printf("option = %c \\n", *option); //三个命令(string型)
if (*option == A)
setLockPskParam.option = OPTION_ADD; //新增密码
else if (*option == U)
setLockPskParam.option = OPTION_UPDATE; //更新密码
else if (*option == D)
setLockPskParam.option = OPTION_DELETE; //删除密码
else
RaiseLog(LOG_LEVEL_ERR, "no such option(%c)!", *option);
return -1;
else
return ret;
if ((objPara = cJSON_GetObjectItem(objParas, "LockPsk")) != NULL)
char *psk = cJSON_GetStringValue(objPara);
if (psk == NULL || strlen(psk) > LOCK_PSK_LENGTH)
RaiseLog(LOG_LEVEL_ERR, "check psk failed!");
return -1;
strncpy(setLockPskParam.password, psk, strlen(psk));
else
return ret;
ret = IotProfile_CommandCallback(CLOUD_COMMAND_SETPSK, &setLockPskParam);
return ret;
第六步:标注GPIO口
识别GPIO口与接入(这里要注意一个接的是正极一个是接地还有一个为信号传输口)
void mqtt_test(void)
IoTGpioInit(9);
IoTGpioSetDir(9, IOT_GPIO_DIR_OUT);
mqtt_connect();
第七步:概括吊起mqtt协议(build.gn版)
与主函数平行的Build.gn,吊起函数与第三方库的内容:
sources = [
"mqtt_test.c",
"mqtt_entry.c"
]
include_dirs = [
"//utils/native/lite/include",
"//kernel/liteos_m/components/cmsis/2.0",
"//base/iot_hardware/interfaces/kits/wifiiot_lite",
"//vendor/hisi/hi3861/hi3861/third_party/lwip_sack/include",
"//foundation/communication/interfaces/kits/wifi_lite/wifiservice",
"//third_party/pahomqtt/MQTTPacket/src",
"//third_party/pahomqtt/MQTTClient-C/src",
"//third_party/pahomqtt/MQTTClient-C/src/liteOS",
"//kernel/liteos_m/kal/cmsis",
"//base/iot_hardware/peripheral/interfaces/kits",
]
deps = [
"//third_party/pahomqtt:pahomqtt_static", //吊起MQTT协议
]
Build.gn:与APP并列的build.gn用于指出要编译的主函数,可以使用startup后面跟要编译的主包也可以直接features进行选中,在这里可以把不需要参与编译的项目通过#给注释掉。
在start_up里的builld.gn:
import("//build/lite/config/component/lite_component.gni")
lite_component("app")
features = [
"mqtt_demo:mqtt_test", //标注主函数,指定位置编译
]
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功能
步骤一:接收服务器的存储代码
exports.i***ta2=function(req,res)
console.log("iot_data:",req)
const url = new URL("Get the URL provided by HUAWEI CLOUD"+req.url) //The address configured inside the forwarding destination
let properties = JSON.stringify(req.body.notify_data.body.services)
console.log("Store data:",properties)
let addArr = [properties]
var addSql = INSERT INTO sesnor_Record(properties) VALUES (?)
var callBack = function(err,data)
console.log("error:"+err)
console.log("Property insertion result:"+JSON.stringify(data))
res.send(data)
sqlUtil.sqlContent(addSql,addArr,callBack)
步骤二:射频贴纸&复旦卡拉取本地方案(实验中,将会在下一次更新全量代码)
写入复旦卡请用第三方的软件,NFC射频贴纸使用应用调试助手。应用调试助手的使用方法和注册方法等请看上下文的介绍以及附带的官网开发文档链接。
void RC522_Config ( void )
uint8_t ucStatusReturn; //Returns the status
uint8_t flag_station = 1; //Leave the flag bit of the function
while ( flag_station )
/* Seek cards (method: all in the range), the first search fails again, and when the search is successful, the card sequence is passed into the array ucArray_ID*/
if ( ( ucStatusReturn = PcdRequest ( PICC_REQALL, ucArray_ID ) ) != MI_OK )
ucStatusReturn = PcdRequest ( PICC_REQALL, ucArray_ID );
if ( ucStatusReturn == MI_OK )
/* An anti-collision operation in which the selected sequence of cards is passed into an array ucArray_ID */
if ( PcdAnticoll ( ucArray_ID ) == MI_OK )
if ( PcdSelect ( ucArray_ID ) == MI_OK )
printf ("\\nRC522 is Ready!\\n");
flag_station = 0;
步骤三:智能窗帘轨解决方案
因为马达可以无限前后方向的对窗帘轨进行拉动所以选择马达来进行拉动。另外要注意马达的功率来判断是否加入继电器,详情请移步源码仓(购买马达时要向供应商索要相关数据,同时向开发板供应商索要已公开的脚位置图)
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;
步骤四:无感配网解决方案(重要ability)
OH设备之间的信息传递利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家与设备之间的互联。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规SoftAP配网方式共存。
相关代码:
teamX/common/iot_wifi/libs/libhilinkadapter_3861.a // 无感配网相关库文件
teamX/common/iot_wifi/libs/libnetcfgdevicesdk.a // 无感配网相关库文件
teamX/common/inc/iot_netcfg_nan.h
teamX/common/inc/network_config_service.h // 无感配网相关头文件
teamX/common/iot_wifi/iot_wifi.c // 相关联网接口
teamX/common/iot_wifi/iot_netcfg_nan.c // 无感配网相关实现
详细的开发流程请移步官网介绍: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
由于移植问题所以官网接入仅提供了安卓的SDK所以软件我们目前智能实现自动打开网页和填入有关信息比对后再复制粘贴出相关的结果,请见谅。
现在在手搓pytorch训练出入库识别,因为没有相关的数据集在飞桨中发现。不过把数据集怼到设备里不是问题,数据集是分场景但是不分对口的操作系统的。这项功能将会在下一次的更新中完成,敬请期待。
2.3 实时摄像功能与智能检测光照值功能(正在实验中,将会在下一次更新全量代码)
官网提供的完整摄像接入方法:https://gitee.com/openharmony-sig/knowledge_demo_smart_home/blob/master/dev/docs/smart_door_viewer_3516/README.md
未发行的明暗检测部分源码:
int GetLightAverageVal(unsigned char cnt)
unsigned short readVal = 0;
unsigned int totalVal = 0, totalCnt = 0;
for(unsigned char i=0; i<cnt; i++)
if(LightSensorVal(&readVal) == IOT_SUCCESS)
totalVal += readVal;
totalCnt++;
usleep(50000);
return (totalVal/totalCnt);
enum ENV_LIGHT_STATE GetEnvLightState(void)
enum ENV_LIGHT_STATE lightState = LIGHT_DAY;
int lightVal = GetLightAverageVal(5);
if(lightVal > ENV_LIGHT_LEVEL_LOWEST)
lightState = LIGHT_NIGHT;
else if(lightVal > ENV_LIGHT_LEVEL_LOW)
lightState = LIGHT_DUSK;
else
lightState = LIGHT_DAY;
return lightState;
2.4 分布式检索功能(实验中)
传统的分布式使用的是Elasticsearch进行,鉴于openharmony能力所以需要开发出对口的ability。
isCreateIfMissing() //分布式数据库创建、打开、关闭和删除
setCreateIfMissing(boolean isCreateIfMissing) //数据库不存在时是否创建
isEncrypt() //获取数据库是否加密
setEncrypt(boolean isEncrypt) //设置数据库是否加密
getStoreType() //获取分布式数据库的类型
setStoreType(KvStoreType storeType) //设置分布式数据库的类型
KvStoreType.DEVICE_COLLABORATION //设备协同分布式数据库类型
KvStoreType.SINGLE_VERSION //单版本分布式数据库类型
getKvStore(Options options, String storeId) //根据Options配置创建和打开标识符为 storeId 的分布式数据库
closeKvStore(KvStore kvStore) //关闭分布式数据库
getStoreId() //分布式数据增、删、改、查。
subscribe(SubscribeType subscribeType, KvStoreObserver observer) //订阅
sync(List<String> deviceIdList, SyncMode mode) 数据同步
开发说明(包括OH分布式文件):
- 构造分布式数据库管理类(创建 KvManagerConfig 对象)
Context context;
...
KvManagerConfig config = new KvManagerConfig(context);
KvManager kvManager = KvManagerFactory.getInstance().createKvManager(config);
- 获取/创建单版本分布式数据库(声明需要创建的单版本分布式数据库ID说明)
Options CREATE = new Options();
CREATE.setCreateIfMissing(true).setEncrypt(false).setKvStoreType(KvStoreType.SINGLE_VERSION);
String storeID = "testApp";
SingleKvStore singleKvStore = kvManager.getKvStore(CREATE, storeID);
- 订阅分布式数据更改(客户端需要实现KvStoreObserver接口&结构并注册KvStoreObserver实例)
class KvStoreObserverClient implements KvStoreObserver()
public void onChange(ChangeNotification notification)
List<Entry> insertEntries = notification.getInsertEntries();
List<Entry> updateEntries = notification.getUpdateEntries();
List<Entry> deleteEntries = notification.getDeleteEntries();
KvStoreObserver kvStoreObserverClient = new KvStoreObserverClient();
singleKvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_ALL, kvStoreObserverClient);
- 构造需要写入单版本分布式数据库的Key和Value(将键值数据写入单版本分布式数据库)
String key = "todayWeather";
String value = "Sunny";
singleKvStore.putString(key, value);
- 构造需要从单版本分布式数据库快照中查询的Key(数据取自单版本分布式数据库快照)
String key = "todayWeather";String value = singleKvStore.getString(key);
- 获取设备列表与同步数据(PUSH_ONLY)
List<DeviceInfo> deviceInfoList = kvManager.getConnectedDevicesInfo(DeviceFilterStrategy.NO_FILTER);
List<String> deviceIdList = new ArrayList<>();
for (DeviceInfo deviceInfo : deviceInfoList)
deviceIdList.add(deviceInfo.getId());
singleKvStore.sync(deviceIdList, SyncMode.PUSH_ONLY);
- 首先get到设备数据交换权限
ohos.permission.DISTRIBUTED_DATASYNC
requestPermissionsFromUser(new String[]"ohos.permission.DISTRIBUTED_DATASYNC", 0);
//然后在AbilitySlice中声明数据库并使用即可,这里不多赘述
- 然后get到分布式的文件目录
Context.getDistributedDir()
//分布式文件与POSIX文件操作接口兼容,应用程序使用Context.getDistributedDir()接口获取目录后,可以直接使用libc或JDK访问分布式文件
- 关于API的开放能力请详见官方文档,这里不再赘述。
- 怼相关接口(正在实验的内容)
SearchAbility searchAbility = new SearchAbility(context);
CountDownLatch lock = new CountDownLatch(1);
searchAbility.connect(new ServiceConnectCallback()
@Override
public void onConnect()
lock.countDown();
@Override
public void onDisconnect()
);
lock.await(3000, TimeUnit.MILLISECONDS);
- 设置搜索属性与插入索引和重构查询等将会在成品完成后(下一次提交中)进行补充,敬请期待!
六.必用帮助
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(事件主题)中自定义订阅与发布,对产品进行定义。 注意:初
以上是关于基于openharmony的储物精灵的主要内容,如果未能解决你的问题,请参考以下文章