本文分享自华为云社区《华为云IoT+OpenHarmony的智能家居开发》,作者:袁睿。
一、选题说明
1. 选题为基于 OpenHarmony 的智能家居,应用场景为户用,受益人群为住户。
2. 开发的软件设备为智能门锁,储物精灵,软硬件开发都有的是光伏逆变器。
3. 解决的问题:
4. 关键功能点:
智能门锁:密码解锁,NFC 解锁,数字管家控制,服务卡片控制
储物精灵:密码解锁,NFC 解锁,防火帘控制,分布式软总线控制
逆变器:单相逆变,隔离拓扑,组件小型化,高转换率与低总谐波失真
二、竞赛开发平台
1. 操作系统:OpenHarmony 3.0
2. 开发软件:VS code(Deveco studio tool),DevEco Studio
3. 开发板:深开鸿 KHDVK-3861B,润和 DAYU200,润和 hispark AI Camera
3. 关于环境:
4. 虚拟机
(1) 虚拟机环境
Ubuntu(华为的硬件开发一般都在此 linux 环境下进行)
虚拟机 Vmware:下载之后使用上述提到的华为云中国镜像
下载 VS code 的 Linux 版与 OpenHarmony3.0 源码
(2)虚拟机环境:
(3) HB 编译插件:
安装:python3 -m pip install --user ohos-build
变量:vim ~/.bashrc
复制粘贴到.bashrc 的最后一行:export PATH=~/.local/bin:$PATH
更新变量:source ~/.bashrc
检查:pip install ohos-build
5. 逆变器的主要硬件选材:
(1)选材方案(选型依据)
Diode(二极管):高频检波电路,小型化,配对二极管不混组
Inductor(感应器):标称电感值,自共振频率(与互感值成正比)直流电阻(尽可能小),额定电流,直流重叠允许电流,温度上升允许电流
Resistor(电阻器):贴片电阻,根据稳定性需求选择薄膜或厚膜
SPICE NMOS:封装尺寸,基本上封装越大越好,能承受的电流就越大;导通电压 Vgs,尽量选择比实际电路中可提供的控制电压小的;导通电阻 Rds,越小越好相应的导通电阻越小、分压越小、发热也越小。寄生电容 Cgs,会影响 mos 的打开速度。寄生电容太大,方波会失真 Rds 越小,Cgs 越大
(2)主要选材
三、方案详述
(一)储物精灵
(1)用户角度:先从用户角度考虑需求与如何使用,再从技术层面解析
(具体用户使用方法这里不多赘述,详细内容直接看下午开发内容)
(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)设备侧
第一步:网络连接 使设备接电后自动联网
我们会在代码中预置热点名称与密码
在创建包后新建 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 等量代换成了自发现热点。
*OpenHarmony_ability 的碰一碰自发现与自配网见下述。
第二步:上报订阅与下发,在此包内创建 main 函数
/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)
我们的原理就是上传画面到云端,然后逐帧分解比对(此功能目前还在完善)
(4)软件侧(偏向软件,但是还是嵌入式开发)
步骤一:接收服务器的存储代码
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)
OpenHarmony 设备之间的信息传递利用特有的 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 // 无感配网相关实现
复制代码
数字管家可以在 gitee 下载源码,在下载的 team_X 中查看详细介绍
步骤五:第三方平台接入
储物精灵 Pro 版(识别功能版):(使用第三方平台:Vuforia)
我们的原理就是上传画面到云端,然后逐帧分解比对(此功能目前还在完善)
第六步:实时摄像功能与智能检测光照值功能(正在实验中)
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;
}
复制代码
第七步:分布式检索功能(实验中)
传统的分布式使用的是 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 分布式文件)
1. 构造分布式数据库管理类(创建 KvManagerConfig 对象)
Context context;
...
KvManagerConfig config = new KvManagerConfig(context);
KvManager kvManager =
KvManagerFactory.getInstance().createKvManager(config);
复制代码
2. 获取/创建单版本分布式数据库(声明需要创建的单版本分布式数据库 ID 说明)
Options CREATE = new Options();
CREATE.setCreateIfMissing(true).setEncrypt(false).setKvStoreType(KvStoreType.SINGLE_VERSION);
String storeID = "testApp";
SingleKvStore singleKvStore = kvManager.getKvStore(CREATE, storeID);
复制代码
3. 订阅分布式数据更改(客户端需要实现 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);
复制代码
4. 构造需要写入单版本分布式数据库的 Key 和 Value(将键值数据写入单版本分布式数据库)
String key = "todayWeather";
String value = "Sunny";
singleKvStore.putString(key, value);
复制代码
5. 构造需要从单版本分布式数据库快照中查询的 Key(数据取自单版本分布式数据库快照)
String key = "todayWeather";String value = singleKvStore.getString(key);
复制代码
6. 获取设备列表与同步数据(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);
复制代码
7. 首先 get 到设备数据交换权限
ohos.permission.DISTRIBUTED_DATASYNC
requestPermissionsFromUser(new String[]{"ohos.permission.DISTRIBUTED_DATASYNC"}, 0);
//然后在AbilitySlice中声明数据库并使用即可,这里不多赘述
复制代码
8. 关于 API 的开放能力请详见官方文档,这里不再赘述。
9. 怼相关接口(正在实验的内容)
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);
复制代码
10. 设置搜索属性与插入索引和重构查询等将会在下一次提交中进行补充。
(二)智能门锁
(上面的储物精灵源码也包括智能门锁的功能实现,这里补充介绍开发)
1. 环境搭建:
(1) 需要手动配置在 deveco tool 里的用户组件
(2) 接舵机的 Gpio 口
这里要注意一个接的是正极一个是接地还有一个为信号传输口
(3) 云端配置
首先在华为云官方获取 Client ID 等身份识别信息,然后在云端的 Topic 中自定义订阅与发布。
在初次开发时可以使用 MQTTX 软件进行了命令的订阅与下发实验,显示在线成功接收到上报和订阅的消息。
这样华为云的配置就成功了
如有需要还要进行产品定义与多功能的增加与实验
2.关于编译:
(1) 在 VS code 编译
点击 build 就可以生成 json 文件啦,编译成功后 upload 就可以直接烧录进去。(注意:在编译之后如果要再编译必须点击 clean 以删除 build 产生的 json 文件避免报错)
(2)在 Ubuntu 通过命令行编译
hb set //这是用于产生 json 文件的
hb build //这是用于编译的,编译后则会在源码的 out 文件夹中产生 bin 文件
hb clean //在 build 一次以后如果如果要再 build 那就必须进行此命令来删除 json 文件
在 build 成功后开发者就会发现在源码中的 out 文件夹中看到 allinone.bin,然后发送到 windows 下使用 Hiburn 进行烧录即可(波特兰最大 3000000,否则会烧坏板子)下图为 HiBurn 的配置方法,点击 Connect 即可烧录。
3.碰一碰卡片(原子化服务)
数字管家需要通过在 APPGallery Connect 中创建项目后添加应用从而获取 Json 文件,然后放在码云中下在的 DistSchedule\netconfig\src\main\resources 中。然后按照文档开发 UI 界面,点击构建的 Generate Key and CSR 创建用户名与密钥进行签名。
用户操作界面:在 slice 目录下新建 xxxSlice.java 文件,通过 addActionRoute 方法为此 AbilitySlice 配置一条路由规则,并且在在应用配置文件(config.json)中注册。在 resources/base/layout 下新建对应 xml 布局文件,在上述两个文件中编写相应的 UI。
数字管家数据处理:从 slice 获取 deviceId:在 onStart 中通过调用 DeviceID 等,获取设备的名称等方便数字管家识别设备。从 slice 页面获取状态:开关锁可以直接调用 intent.getBooleanParam 来确定是进行开关锁还是对门锁的日程进行编排。
编写设备控制命令的解析:在 CommandUtil 中根据具体设备定义 profile,来新增获取命令和解析命令的方法,用于设备在本地调用 sendCommand 来发送命令和解析。
配置设备端信息:在 DeviceData 的 initData 中,根据设备 ProductID 添加设备图片 ID、跳转的 action 参数和解析方法,配置完成后设备列表页、用户页面等都能通过该配置进行图片加载、路由跳转和解析。
最后进行接口对接与 NFC 写入就可以了(通过应用调试助手写入 NFC 识别详细用于快速让手机识别到设备从而吊起数字管家实现鸿蒙的 Ability)
(三)逆变器
1. 拓扑图
设计的单相逆变器,拥有隔离拓扑,通过控制 GaN(HEMT)的高频开关实现逆变
关于桥臂:两个半桥产生中性点电压,另外两个半桥产生线电压,最后一个半桥作为有源滤波器。
2.现在 STM32f407 兼容了 OpenHarmony 3.2 bata 版,因为 f4 系列软合了 dsp 处理所以无需另外使用 dsp 从处理器。考虑到尽量减少直流侧输入电流纹波,输出的正弦波尽可能的平滑与减小总谐波失真,设计了一种并联有源滤波器,它比在输入端使用批量电容更有效地补偿纹波。
3.考虑到大部分 EDA 的元件库原件都不全,我在 kicad 按照厂家提供的数据手册画了个原件,并按照例出的参数进行了标注。
4. 关于电流与电压的总谐波失真等:有源滤波器工作在更高的电压变化下将相应的能量存储在陶瓷电容器中,陶瓷电容器的电容随着电压的降低而增加。通过算法保持 Vin 稳定同时允许有源滤波器产生大的波纹。
输出电流结合电磁屏蔽的开环霍尔传感器形成非常紧凑的测量装置提供电流解耦并降低对共模和寄生感应噪声的敏感性。特定的 GaN 控制调制降低了滤波器电感中的电流可以在不达到饱和水平的情况下降低其核心尺寸。
5. 关于硬件选材:在上文的 二.竞赛开发平台 的逆变器中有介绍
6.通讯部分
(1)分布式软总线
基于 UDP 的 coap 协议,OpenHarmony 特有分布式软总线。
编程步骤:
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 软件进行相关的发送与接收,并查看打印信息。因为与下文介绍的 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 中。
7.服务卡片
(1)服务卡片原理
(2)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#/
②逻辑处理:
(i)用户操作界面:在 slice 目录下新建 xxxSlice.java 文件,通过 addActionRoute 方法为此 AbilitySlice 配置一条路由规则,并且在在应用配置文件(config.json)中注册。在 resources/base/layout 下新建对应 xml 布局文件,在上述两个文件中编写相应的 UI。
(ii)数字管家数据处理:从 slice 获取 deviceId:在 onStart 中通过调用 DeviceID 等,获取设备的名称等方便数字管家识别设备。从 slice 页面获取状态:开关锁可以直接调用 intent.getBooleanParam 来确定是进行开关锁还是对门锁的日程进行编排。
(iii)编写设备控制命令的解析:在 CommandUtil 中根据具体设备定义 profile,来新增获取命令和解析命令的方法,用于设备在本地调用 sendCommand 来发送命令和解析。
(iv)配置设备端信息:在 DeviceData 的 initData 中,根据设备 ProductID 添加设备图片 ID、跳转的 action 参数和解析方法,配置完成后设备列表页、用户页面等都能通过该配置进行图片加载、路由跳转和解析。
(v) NFC 写入:最后进行接口对接与 NFC 写入就可以了(通过应用调试助手写入 NFC 识别详细用于快速让手机识别到设备从而吊起数字管家实现鸿蒙的 Ability)可以写到开发板的 NFC 预存区,也可以写在 huawei share 的碰一碰卡片上。(目前这两种写法都可以写无数次,在下一次写入时会自动清除上一次所写的)
③开发方式:
(i) 用户操作界面:通过桌面可以在卡片中点击相关服务,卡片中可以呈现一个或多个服务。
(ii)工作原理:通过嵌入到 UI 界面拉起那款应用的服务(可以通过缓存实现快速打开)从而起到交互功能的原子化服务。
(iii)生命周期管理:对设备使用方的 RPC 对象进行管理,请求进行校验以及对更新后的进行回调处理。
(iv)卡片尺寸:目前官方有四种尺寸,可以在 new 中自己选中喜欢的尺寸。
(v)上手开发:新建一个服务卡片
选择自己所需的卡片框架
(vi)开发环节:创建完之后然后就可以看到在原有的 subject 中生成了 config.json 文件。js 默认配置了卡片大小等信息,froms 下的是 ability 中生命周期管理的核心部分(用于回调),会在主函数中实现调用。
要在这里把 false 改成 true。
上图的文件包为主要的开发位置,开发者动的是 index 下的三个包。
完成签名之后在在线调试的实验机器上运行后就会产生一张纯的 FA 卡片了,此时环境已经搭建完毕。
本地缓存调取:src 在 main 下的 resources 中建 rawfile 用于存放缓存,在编译时候打包进 hap 中怼到鸿蒙设备中即可 get 到。
下面以开发 1*2 的 mini 卡片为例,在本地预置了缓存文件后我们目光转向卡片,继续把播放按钮与卡片解耦开,通过 hml 塞入显示信息等。isWidget 当 true 时,card_containerdiv 就会变为 div 布局。Ispause 为 true 时,按钮呈现播放;为 false 时,显示暂停按钮。
在 css 文件采用原子布局的 display-index。display-index 的值越大,则越优先显示。在 main 中的 onCreateForm 里 isMiniWidget 的 data 设置为 true。
在.json 和 main 中相对应的地方添加点击事件,到此为止就可以通过点击卡片就可以得到 start 与 stop 的互动了。做完显示界面以后,接入界面与预置的本地缓存,然后封装即可。
上图上中下分别是更新(onUpdateForm),删除(onDeleteForm),事件消息(message),
更新(onUpdateForm): 卡片更新与持久化储存卡片,定时更新与请求更新时进行调用。
删除(onDeleteForm):用于删除卡片时调用。 图三:formid&massage,接收通知。一张 Fa 卡片创建时需要满足的基本功能:布局加载~请求数据(ohos&intent)~产生卡片(long&生成 ID 用于调用){通过枚举值得到}。
这样一张服务卡片就开发好了。
四、创新点描述
1. 关于智能门锁:
基于 OpenHarmony 开发,使用原子化服务,拥有密码解锁,NFC 解锁,数字管家控制等功能。
2. 关于储物精灵
基于 OpenHarmony 开发,使用原子化服务,密码解锁,NFC 解锁,防火帘控制,分布式软总线控制等。
3. 关于逆变器
基于 OpenHarmony 开发,拓扑架构大幅度缩小转换器桥臂和 EMI 滤波器的尺寸,在算法使用 CEC 加权效率设计与峰值电压追踪,通过品质因数公式 FOM 算出使用合适的 GaN 半导体选型结合五个桥臂的设计可以最小化逆变器的能量传递。
五、成果展现
1. 编译成功
2. 动图演示(导入到 word 中是动图,word 可能无法显示出动图效果所以把相关图片动图在上传文件夹中备份了一份)
以下动图分别是门锁的舵机驱动,NFC 打卡,智能门轨的演示动图。
点击关注,第一时间了解华为云新鲜技术~
评论