万字长文教你实现华为云IoT+OpenHarmony智能家居开发

本文分享自华为云社区《华为云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. 关于环境:

操作系统:Ubuntu 编译构建:Python

包管理工具:HPM NPM 环境:Node.js

烧录软件:Hiburn USB串口驱动:CH341SER.exe

本地ssh:finalshell ssh文件对比:Beyond Conpare

4. 虚拟机

(1) 虚拟机环境

Ubuntu(华为的硬件开发一般都在此linux环境下进行)

虚拟机Vmware:下载之后使用上述提到的华为云中国镜像。

下载VS code的Linux版与OpenHarmony3.0源码。

(2)虚拟机环境:

将Ubuntu Shell环境修改为bash:ls -l /bin/sh

在下载VS code后下载华为硬件编制插件(Device tool)

(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)主要选材

半导体选材:国产半导体RX65T125PS2A

电源IC选材:国产IC芯片ID7s625

DSP处理器:洞察到STM32系列软合了DSP处理器的功能。

三、方案详述

(一)储物精灵

(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中声明数据库并使用即可,这里不多赘述

9. 关于API的开放能力请详见官方文档,这里不再赘述。

10. 怼相关接口(正在实验的内容)

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);

11. 设置搜索属性与插入索引和重构查询等将会在下一次提交中进行补充。

(二)智能门锁

(上面的储物精灵源码也包括智能门锁的功能实现,这里补充介绍开发)

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打卡,智能门轨的演示动图。

点击关注,第一时间了解华为云新鲜技术~

相关推荐

鸿蒙案例上新啦 | 出门打车、到家上课:25个模板带你一屏打通日常生活

如何合理拆分页面组件、如何建立单一可信的状态源、如何让用户在每一步操作中都获得明确反馈。鸿蒙开发实践中心以标准化的应用与元服务模板为入口,将清晰的业务架构与可复用的代码结构直观呈现。不妨立即挑选一个感兴趣的方向进入云端环境,从页面布局、组件封装、状态管理到路由组织,亲手跑通你的鸿蒙实战项目。

华为云官方博客 308

LakeAgent:DWS数据仓库的第二大脑,告别 “翻译式运维”,迈入智能自治时代

和普通 SQL 改写工具不同,LakeAgent 自动做多方案代价对比,主动规避负优化陷阱:补充分区键过滤实现剪枝,扫描分区由 37 收敛至 1,执行代价从 758.28 降至 113.74;未来的数据仓库,将不止能算、能存,更能看懂自己、诊断自己、理解自己。长久以来,数仓的智能都依附于人:信号需要人解读,瓶颈需要人研判,经验困在个体里,无法随底座永续生长。CPU 高、CPU 告警、CPU 诊断、CPU 使用率过高、CPU 满。语法、错误码、计费、性能调优、最佳实践、故障处置等全场景内容。

华为云官方博客 546

如何使用RDS Supabase后端BaaS能力:文件存储(File Storage)

• product-images(公开桶,允许公共读取):策略名称"Public read for product-images",允许SELECT,目标角色anon,策略定义bucket_id = 'product-images'。上传/删除需另配INSERT/DELETE策略,详见SQL。• product-specs(私有桶,仅认证用户访问自己的文件夹):建议拆为上传、读取、删除三条策略,策略定义均为 bucket_id = 'product-specs' 且仅限本人文件夹(详见下文 SQL)。

华为云官方博客 465

如何使用RDS Supabase后端BaaS能力:实时订阅与推送(Realtime)

Realtime 把数据库的每次变更同步到客户端:表里增、删、改一行,订阅者无需刷新页面或主动请求即可秒级收到。三大能力各司其职:• Postgres Changes — 监听表的 INSERT/UPDATE/DELETE,变化即通知• Broadcast — 客户端之间直接互发,适合瞬时通知、协同编辑• Presence — 跟踪频道里“谁在线”典型场景有实时数据展示(订单、库存、监控指标即时刷新)、即时通讯(消息收发、在线名单、“对方正在输入”)、协同编辑与实时通知。

华为云官方博客 458

AI Agent如何通过MCP连接到华为云RDS Supabase应用平台

Schema元数据列出所有可访问的表(按schema分组)。Schema元数据列出表的列及元数据(类型/默认值/约束)。Schema元数据列出所有约束(PK/FK/UNIQUE/CHECK/EXCLUDE)。可按schema/表/类型过滤。Schema元数据列出所有外键关系。可按schema/表过滤。Schema元数据列出所有索引及定义和大小。可按schema/表过滤。Schema元数据列出已应用的数据库迁移记录。Schema元数据列出已安装的PostgreSQL扩展。

华为云官方博客 465

华为大数据Flink让每一 CU 算力,跑出开源 5.74 倍的性能

1. 开源Apache Flink 已成为业界流计算事实标准,但在极端重负载场景下存在性能瓶颈。2. 华为大数据Flink通过多项自研深度优化,在执行计划、算子执行、状态存储三个层面构建协同的优化体系。3. Nexmark Benchmark 测试结果:整体性能提升 5.74 倍,极端重负载场景性能领先最高 18.3 倍。4. 100% 兼容 Apache Flink 1.20,业务代码零改造,无缝迁移。

华为云官方博客 374

LakeAgent实战 | 业务分析篇从一句提问到业务分析

本文以“订单金额变化”分析为例,展示LakeAgent如何通过自然语言提问,结合DataArtsStudio的数据资产与专业技能,帮助业务人员快速定位数据、生成SQL、追踪血缘、生成报告。从问题出发,逐步实现数据发现、变化洞察、原因追溯与团队协作,最终形成可复用的分析流程与企业规则,显著提升数据分析效率与一致性。

华为云官方博客 383

华为云RDS Supabase AI应用已开放公测 :30分钟搭建AI应用后端

认证、存储、实时推送、数据库API——4个后端模块,传统开发需要数周。今天,你只需要30分钟。AI创业公司的全栈开发工程师。上周老板说"下周要上线的AI对话应用,后端你来搞"——认证鉴权、数据库接口、文件存储、实时推送,每一个模块都需要数周搭建,而市场不会等你。小张打开华为云RDS Supabase,30分钟搭好认证、数据库API、对象存储,剩下的时间全用来打磨AI交互体验。基于开源Supabase+华为云RDS PostgreSQL全托管能力,为开发者提供。

华为云官方博客 390

如何使用RDS Supabase后端BaaS能力:数据库函数(Database Function)

上一篇介绍了 Database Data API——建表即自动生成增删改查接口,解决单表读写,但聚合统计、跨表事务、字段自动维护等逻辑已超出其能力边界。本篇推进到数据库函数(Database Function):把业务逻辑封装进数据库,前端通过 .rpc() 调用,由触发器自动响应数据变更。上一篇中,小张基于 Data API 完成了产品目录的建表与基础读写。但新需求随之而来:按分类统计产品数与价格区间、上架新品同时写产品表与定价表、产品修改后自动刷新更新时间。

华为云官方博客 456

如何使用RDS Supabase后端BaaS能力:创表即生成API(Database Data API)

认证、存储、实时推送、数据库API——4个后端模块,传统开发要数周。本系列逐个拆解,首篇聚焦数据库API(Database Data API):建表即得 API,无需手写后端。小张,AI创业公司的全栈工程师。老板让他做个产品目录页,展示产品与价格、支持上下架。传统做法要搭后端、写接口、配权限,工期少说1-2周;而在华为云RDS Supabase,建两张表、应用 SDK 直连,几分钟增删改查全跑通。

华为云官方博客 474

从一句话到一个能出去的链接,AI开发者空间做了什么

自行搭建的 Agent、正在使用的开源项目、团队既有的工具,都可以接入,可以通过AI创建或者手动搭建环境;华为云 AI开发者空间——面向开发者的云端一站式开发平台,做的就是把这段路接上:环境免安装,浏览器打开即为完整的 Linux 运行环境,预置的 AI 智能体开箱即用。第一个任务建议选能出结果的:做一个游戏、生成一个页面、创建一台实例,从需求走到可访问的链接,一次就能看清这个环境在做什么。沙箱是一个完整的 Linux 环境,依赖、服务、端口都是真实的,不是模拟。环境释放后,权限随之失效。

华为云官方博客 422

华为云码道配置自定义大模型,四种方式完整实操

案例介绍。

华为云官方博客 617

图数据库:实现穿透式监管的数据基座

穿透式监管标志着国企监管从“管企业”向“管资本”转型过程中的关键一跃。而图数据库正是这一跃的技术支点。从股权穿透到资金链穿透,从供应链穿透到数据穿透,华为GES图数据库正在将“穿透式监管”从理念变为可落地、可操作的实践。无论是天眼查式的全量链路查询,还是某集团财经的税务规则判断,抑或是芯片供应链式的部件风险分析,乃至通过Palantir构建决策知识图谱,其底层逻辑都是一致的——用图的力量,穿透一切形式的“隔离层”,直抵风险本质与责任核心。

华为云官方博客 661

《公共云三十问》正式收官,奔赴企业AI百场景新征程

今年6月底,公共云产业联盟重点推出《公共云三十问》专栏,以每周三问的节奏,连载十周,解读《先进公共云》白皮书,分享国计民生系列案例,旨在凝聚共识、推动落地实践。同时,专栏分析公共云在产业升级、民生福祉、科技创新、全球发展等方面的作用,梳理中国特色公共云发展路径:以普惠云拓展用云广度,以智算云提升用云深度,以自主云夯实用云厚度,以全球云打开用云跨度。这十问从专属云、行业云切入,深入政务、金融、交通、电力、钢铁、化工、油气、制造等国计民生场景,回答公共云“怎么用”。

华为云官方博客 471

开发者小技巧|IAM配置,让码道代码智能体连续30天免登录

案例介绍。

华为云官方博客 644

码道深度对接CodeArts,让需求自动落地为代码

案例介绍。

华为云官方博客 719

【公共云三十问 之三十】拥抱AI,企业如何建设面向智能体的专属云?

企业可分阶段建设覆盖开发、运行、运营及安全的一体化智能体平台——从构建一站式、高低码协同的智能体开发平台,到打造高性能、极致弹性的运行时环境,再到搭建AgentOps驱动的持续运营与运维体系,同时构建端到端、多层次的全链路安全防护体系。在数据湖建设方面,遵循“存量复用、平滑演进、AI导向、分步迭代”原则,分三阶段推进——打造AI多模数据湖核心能力、完成存算分离与统一元数据管理、实现闭环赋能全面支撑智能体规模化落地,在保护既有投资的前提下将传统数据平台转型为AI数据专属供给平台。

华为云官方博客 821

【公共云三十问 之二十九】国计民生之制造:制造云如何重塑数智时代的新质生产力?

平台打通软件交付的完整路径,覆盖需求管理、代码托管、代码检查、编译构建、制品仓库、部署、测试等关键环节,支持大模型能力融入研发全流程,实现从需求到交付的端到端管理。通过构建“研发一朵云”,方案打破地域与组织壁垒,实现跨团队协同开发,有效缩短软件交付周期,助力工业制造企业从“传统研发”走向“敏捷研发”,加速数智化转型进程。当制造云真正成为数据流通的平台、AI创新的底座,公共云的社会价值将从基础设施层向生产场景层持续延伸,为新型工业化建设和制造强国战略注入源源不断的动力,重塑数智时代的中国制造新质生产力。

华为云官方博客 789

告别重复手工迁仓,码道MCP+Skill实现仓库批量迁移

案例介绍。

华为云官方博客 790

【公共云三十问 之二十八】国计民生之油气:智算云如何叩响数智时代的“地心蓝金”?

西南油气田以“向地球深部要资源”为战略方向,联合以华为云Stack为代表的公共云服务商共同打造统一算力底座,建设西南区域数据湖与通用技术平台,构建“勘探开发工程技术、天然气生产过程管控、天然气产运储销”等三个一体化协同应用场景,支撑油气勘探、开发生产、管输储运等全业务链的数智化转型。当智算云真正成为数据流通的平台、AI创新的底座,油气田得以将算力效能、数据价值与智能技术深度融入勘探开发全链条,将“看不见的算力”转化为“摸得着的储量”,以智能化手段持续降低勘探风险、缩短发现周期、提高采收效率,确保生产安全。

华为云官方博客 613

【公共云三十问 之二十七】国计民生之化工:数智云如何重塑传统化工新范式?

在架构方面,方案构建云边协同的统一体系,涵盖AI协同、数据协同与运营协同三大维度——总部集中训练大模型,边缘节点负责推理执行,云边持续迭代,实现“边用边学”的智能闭环,模型平均精度提升20%以上。此外,方案提供一站式企业级工业物联网平台、数据湖与数据治理平台,覆盖工业数据分析“采、存、算、管、用”全流程,打通生产现场与管理系统间的数据壁垒,有效消除数据孤岛。层面,方案充分发挥软硬协同优势,实现计算、存储、网络与云平台的深度协同,训练线性度超95%,为大模型训练推理提供强劲算力支撑。▲化工“数智”新模式。

华为云官方博客 663

【公共云三十问 之二十六】国计民生之钢铁:数智云如何淬炼中国钢铁新力量?

同时,构建云边端协同的统一架构,支撑集团总部与各生产基地两级协同发展——总部集中训练大模型,边缘节点负责推理执行,云边持续迭代,实现训推一体化的资源调度与高效协同,为钢铁行业的数智化转型构建了坚实的算力底座。层面,方案构建覆盖“工业数采→边缘IT/OT融合→统一数据湖→数据治理平台→数据应用”全链路的一站式数据治理与流通平台,打通生产现场与管理系统间的数据壁垒,让数据“采得到、流得动、用得好”,加速数据价值从生产现场向经营决策层流动释放。湘潭钢铁集团的钢铁数智云,是钢铁工业智能云建设的标杆案例。

华为云官方博客 526

LakeWatch——MRS 智能运维Agent

从资源池、队列、作业等多个维度,对集群CPU与内存资源进行精细化分析与对比,直观呈现不同时间段的空闲、满载等使用状态,识别资源浪费,指导治理优化,为扩缩容决策提供数据支撑。对HDFS存储进行多维度深度分析(目录、用户、用户组等),涵盖使用量、增量、文件大小分布及数据热度等指标,帮助快速识别增长较快的目录、长期未访问的文件,统计目录文件数量与小文件占比,评估HDFS总存储量与扩缩容需求,为存储治理与资源规划提供全面数据支撑。Agent主动巡检,识别异常趋势并提前预警,让运维从"事后处理"转向"事前预防"。

华为云官方博客 515

华为云专题文档发布:丰富的AI案例、选型指导和概念解读

发布 “云服务选型”、“云计算概念”专题,提供云服务选型指导,助您选择匹配业务的云服务,解读云计算相关概念,开启云上学习之路。开发者工具和文档专区上线,提供从学习、实践到反馈的全链路支持。帮助中心新增100余篇AI案例。

华为云官方博客 286

一个人,也能用AI做点“星东西”

华为云星耀创想者大赛-2026华为云OPC创意大赛等你来~

华为云官方博客 289

从需求到可执行工作项:MCP+Skill 让智能体规划真正落地

回顾整个体验,以前让智能体帮我拆需求,它是洋洋洒洒给我列了一大篇 Markdown,看着挺像那么回事,但最后还是得我一条条往 CodeArts Req 里搬,搬完再手动挂父子关系、排日期,拆需求五分钟,搬运两小时。现在好了,MCP 给智能体接上了"手",Skill 给它装上了"项目经理的脑子",一句话下去,34 个工作项连带排期、层级关系全给我建好了,甘特图一拉,齐刷刷都在两周内,节省了多少工作量啊,美滋滋。本次实操在码道代码智能体基础上,构建了一套"需求→可执行工作项"的自动化规划方案。

华为云官方博客 574

华为云码道「码力续航计划」更新!

✅上新GLM‑5.3‑Flash,高速响应,代码生成、工程解析、Agent调试高效输出。新增GLM‑5.3‑Flash福利模型,每日解锁1000万免费Tokens。● 本次活动共发放总量价值100万元Tokens,发完即止!把耗算力的编码重活,交给华为云码道代码智能体🚀🚀。● 每日额度准时刷新,当日清零、次日满血重置。✅复杂算法、大项目解析、批量代码生成随便跑。✅不用抠额度、不用算消耗,放开调试大胆迭代。✅无需额外操作,登录即送,直接切换模型使用。● 把算力交给码道,让撸码不再内耗👏🏻。

华为云官方博客 684

【公共云三十问 之二十四】国计民生之交通:交通云如何铺就数智通衢新坦途?

交通行业作为支撑实体经济的关键基础设施,涵盖公路、铁路、机场、港口、地铁等众多子领域,其中,公路网络作为综合交通运输体系的基础骨架,承担着全国超过70%的客货运量,其数字化、智能化转型的需求尤为迫切。方面,提供Agent开发框架、知识库管理、RAG工程、意图识别、静态应用编排及模型评测等能力,支持客户将行业知识、业务流程与AI能力深度融合,快速构建面向收费稽核、路网调度、应急处置等场景的智能应用。开发并实现落地应用,全面覆盖建设、管理、养护、运营、服务、安全、办公七大业务领域。

华为云官方博客 461

【公共云三十问 之二十五】国计民生之电力:电力云如何点亮数智电力新引擎?

方案构建了覆盖数据获取、质量评估到标准化处理的全链路能力。基于公共云服务商智算底座及气象大模型,通过时空图神经网络重新建模产流机制,中国华电建成超50TB多源融合数据集,突破时空建模与机理融合等五大关键技术,短期径流预测精度较传统机理模型提升5个百分点以上,实现15天中期径流预测精度超85%,推理效率达秒级。基于气象大模型的基础架构与预训练能力,客户可直接在大模型之上开展增量训练与微调,充分利用场站所在区域的高精度历史数据(如3公里、1公里分辨率),针对不同场站和区域的地形地貌与气候特征进行模型调优。

华为云官方博客 380

邀请函|第三届“兴智杯”全国人工智能创新应用大赛开幕式,期待您现场莅临

AI 算法工程师、大模型应用开发、智能编码、国产软硬件栈开发者、开源开发者。大赛官方参赛对象就包含企事业单位开发者团队、个人开发者中华人民共...诉求:了解本届赛题方向、对接算力 / 技术资源、认识同行、获取参赛信息。意向参赛团队、往届参赛选手、技术团队负责人、个人独立开发者。

华为云官方博客 479
上一篇: GaussDB(for Redis)游戏实践:玩家下线行为上报
下一篇: 一文详解数据仓库的物理细粒度备份恢复
华为云开发者联盟
华为云开发者联盟 企业官方账号 企业官方账号
博客等级 码龄10年 72万+粉丝 5985原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值