OpenHarmony南向开发案例:【智能体重秤】

一、简介

本demo基于OpenHarmony3.1Beta版本开发,该样例能够接入数字管家应用,通过数字管家应用监测体重秤上报数据,获得当前测量到的体重,身高,并在应用端形成一段时间内记录的体重值,以折线图的形式表现出来,根据计算的BMI值来提醒当前身体健康状态,推送健康小知识。

1.交互流程

如上图所示,智能体重称整体方案原理图可以大致分成:智能体重称设备、数字管家应用、云平台三部分。智能体重称通过MQTT协议连接华为IOT物联网平台,从而实现命令的接收和属性上报。

2.实物简介

如上图示,左边为xr806模组,右边为超声波测距模块,echo脚连接PA19,Triq脚连接PA20,Vcc脚连接5V电源,Gnd脚接地,

如上图示,右边为称重模块,clk脚接PB15,dt脚接PB14,vcc脚接5V,gnd脚接地,称重传感器红色线接E+,黑色线接E-,白色线接A-,绿色线接A+

左边xr806模块左下角k1按键,长按k1按键不放,同时上电,4-5秒后松开按键,可以清除已保存得配网信息

xr806模块,在设备正常工作后,按k1按键,可以初始化当前得重量为0,高度为0

3.实物操作体验

二、 快速上手

1.硬件准备
  • xr806模组
  • hcsr04超声波模块
  • hx711称重模块带支架托盘
  • 预装HarmonyOS手机一台
2、环境准备

参照文档: XR806快速上手指导文档

3、编译前准备

鸿蒙开发文档参考:qr23.cn/AKFP8k点击或者复制转到即可。

设备侧代码下载

下载方式:使用git 命令下载,指令如下(用户也可以根据需要将该仓库fork到自己的目录下后进行下载)

cpp 复制代码
cd ~
git clone git@gitee.com:openharmony-sig/knowledge_demo_smart_home.git
代码拷贝
cpp 复制代码
cp -rfa  ~/knowledge_demo_smart_home/dev/team_x  ~/openharmony/vendor/
cp -rfa  ~/knowledge_demo_smart_home/dev/third_party/iot_link  ~/openharmony/third_party/
SOC代码下载替换

当前官方soc代码由于DHCP暂未适配,所以暂时不支持AP模式,这时需要下载并替换之前SOC代码。如果官方soc代码已修复该问题,可忽略此步骤。

cpp 复制代码
git clone https://gitee.com/moldy-potato-chips/xr806_-ap_mode.git
mv ~/openharmony/device/soc/allwinner ~/allwinner.org			// 不建议直接删除,
cp -raf xr806_-ap_mode ~/openharmony/device/soc/allwinner

整合并修改完成后的目录结构如下图

修改文件
  • 修改编译依赖

打开 device/soc/allwinner/xradio/xr806/BUILD.gn,添加应用依赖(deps字段):

cpp 复制代码
module_group(module_name) {
  modules = [
      "src",
      "project",
      "include",
  ]
  configs = [
    ":SdkLdCconfig",
  ]
  deps = [ "//vendor/team_x/smart_weight_scale/demo_smart_weight_scale:smart_weight_scale" ]
}
  • 修改编译方式

将demo依赖的库编译方式(static_library)修改为(source_set):

具体依赖查看demo_smart_weight_scale目录下的BUILD.gn:

cpp 复制代码
    deps = [
       "../../common/iot_wifi_xradio:iot_wifi",
       "../../common/iot_cloud:iot_cloud",
       "//third_party/cJSON:cjson",
       "../../common/iot_boardbutton_xradio:iot_boardbutton",
       "../../common/iot_boardled_xradio:iot_boardled_xradio",
    ]

其中//third_party/cJSON目录下的BUILD.gn建议参照下面的修改:

cpp 复制代码
source_set("cJSON") {
  sources = [
    "cJSON.c",
    "cJSON_Utils.c",
  ]
  ldflags = [ "-lm" ]
}

third_party/iot_link目录下的各级使用到的BUILD.gn也需要将编译方式修改为source_set,或者将所有需要编译的文件放在iot_link目录的BUILD.gn中,如下:

cpp 复制代码
source_set("iot_link") {
    sources = [
        "link_log/link_log.c",
        "link_misc/link_random.c",
        "link_misc/link_ring_buffer.c",
        "link_misc/link_string.c",
        "network/dtls/dtls_al/dtls_al.c",
        "network/dtls/mbedtls/mbedtls_port/dtls_interface.c",
        "network/dtls/mbedtls/mbedtls_port/mbed_port.c",
        "network/dtls/mbedtls/mbedtls_port/timing_alt.c",
        "network/mqtt/mqtt_al/mqtt_al.c",
        "network/mqtt/paho_mqtt/port/paho_mqtt_port.c",
        "network/mqtt/paho_mqtt/port/paho_osdepends.c",
        "network/mqtt/paho_mqtt/paho/MQTTClient-C/src/MQTTClient.c",
        "network/mqtt/paho_mqtt/paho/MQTTPacket/src/MQTTConnectClient.c",
        "network/mqtt/paho_mqtt/paho/MQTTPacket/src/MQTTConnectServer.c",
        "network/mqtt/paho_mqtt/paho/MQTTPacket/src/MQTTDeserializePublish.c",
        "network/mqtt/paho_mqtt/paho/MQTTPacket/src/MQTTFormat.c",
        "network/mqtt/paho_mqtt/paho/MQTTPacket/src/MQTTPacket.c",
        "network/mqtt/paho_mqtt/paho/MQTTPacket/src/MQTTSerializePublish.c",
        "network/mqtt/paho_mqtt/paho/MQTTPacket/src/MQTTSubscribeClient.c",
        "network/mqtt/paho_mqtt/paho/MQTTPacket/src/MQTTSubscribeServer.c",
        "network/mqtt/paho_mqtt/paho/MQTTPacket/src/MQTTUnsubscribeClient.c",
        "network/mqtt/paho_mqtt/paho/MQTTPacket/src/MQTTUnsubscribeServer.c",
        "oc_mqtt/oc_mqtt_al/oc_mqtt_al.c",
        "oc_mqtt/oc_mqtt_profile_v5/oc_mqtt_profile.c",
        "oc_mqtt/oc_mqtt_profile_v5/oc_mqtt_profile_package.c",
        "oc_mqtt/oc_mqtt_profile_v5/oc_mqtt_event.c",
        "oc_mqtt/oc_mqtt_tiny_v5/oc_mqtt_tiny.c",
        "oc_mqtt/oc_mqtt_tiny_v5/hmac.c",
        "queue/queue.c",
    ]
    
    cflags = [ "-Wno-unused-variable" ]
    cflags += [ "-Wno-unused-but-set-variable" ]
    cflags += [  "-Wno-sign-compare" ]
    cflags += [  "-Wno-unused-parameter" ]
    cflags += [  "-Wno-unused-function" ]

    ldflags = [ "-Wl,-rpath-link=//device/xradio/xr806/xr_skylark/lib" ]
    ldflags += [ "-lmbedtls" ]

    include_dirs = [
        "inc",
        "link_log",
        "link_misc",
        "queue",
        "oc_mqtt/oc_mqtt_tiny_v5",
        "oc_mqtt/oc_mqtt_profile_v5",
        "oc_mqtt/oc_mqtt_al",
        "network/dtls/mbedtls/mbedtls_port",
        "network/mqtt/paho_mqtt/port",
        "network/mqtt/paho_mqtt/paho/MQTTClient-C/src",
        "network/mqtt/paho_mqtt/paho/MQTTPacket/src",
        "//third_party/mbedtls/include/",
        "//third_party/mbedtls/include/",
        "//third_party/cJSON",
        "//kernel/liteos_m/components/cmsis/2.0",
        "//device/xradio/xr806/xr_skylark/include/net/mbedtls-2.2.0/",
    ]

    defines = [
        "MQTTCLIENT_PLATFORM_HEADER=paho_osdepends.h",
        "WITH_DTLS",
        "MBEDTLS_AES_ROM_TABLES",
        "MBEDTLS_CONFIG_FILE="los_mbedtls_config_dtls.h"",
        "CONFIG_DTLS_MBEDTLS_CERT",
        "CONFIG_DTLS_MBEDTLS_PSK",
        "CFG_MBEDTLS_MODE=PSK_CERT",
        "CONFIG_OC_MQTT_TINY_ENABLE=1"
    ]
}
  • 修改iot_link中的部分文件
  1. third_party/iot_link/network/mqtt/paho_mqtt/port/paho_mqtt_port.c

    测试发现,当fd为0的时候,在执行recv时会立马返回-1,因此做下面规避操作。

    cpp 复制代码
    static int __socket_connect(Network *n, const char *host, int port)
    {
    	...
    	int tmpfd = socket(AF_INET,SOCK_STREAM,0); // to skip fd = 0;
    	fd = socket(AF_INET,SOCK_STREAM,0);
    	if(fd == -1) {
    		return ret;
    	}
    	close(tmpfd);       // to skip fd = 0;
    	...
    }

    系统setsockopt函数未适配,因此需要做下面的修改:

    cpp 复制代码
    static int __socket_read(void *ctx, unsigned char *buf, int len, int timeout)
    {
    	int fd;
        int ret = 0;
    #if 0
    	struct timeval timedelay = {timeout / 1000, (timeout % 1000) * 1000};
        if(NULL== uf)
        {
            return ret;
        }
        
        fd = (int)(intptr_t)ctx;  ///< socket could be zero
    
        if (timedelay.tv_sec < 0 || (timedelay.tv_sec == 0 && timedelay.tv_usec <= 0))
        {
            timedelay.tv_sec = 0;
            timedelay.tv_usec = 100;
        }
        
        if(0 != setsockopt(fd,SOL_SOCKET,SO_RCVTIMEO,&timedelay,sizeof(struct timeval)))
        {
            return ret;  //could not support the rcv timeout
        }
        int bytes = 0;
        while (bytes < len) {
            int rc = recv(fd, &buf[bytes], (size_t)(len - bytes), 0);
            printf("[%s|%s|%d]fd = %d, rc = %d\n", __FILE__,__func__,__LINE__, fd, rc);
            if (rc == -1) {
                if (errno != EAGAIN && errno != EWOULDBLOCK) {
                    bytes = -1;
                }
                break;
            } else if (rc == 0) {
                bytes = 0;
                break;
            } else {
                bytes += rc;
            }
        }
        return bytes;
    #else
    	int bytes = 0;
        fd_set fdset;
    
        struct timeval timedelay = {timeout / 1000, (timeout % 1000) * 1000};
        if(NULL== buf)
        {
            return ret;
        }
        
        fd = (int)(intptr_t)ctx;  ///< socket could be zero
    
        if (timedelay.tv_sec < 0 || (timedelay.tv_sec == 0 && timedelay.tv_usec <= 0))
        {
            timedelay.tv_sec = 0;
            timedelay.tv_usec = 100;
        }
        timedelay.tv_sec = 2;
        FD_ZERO(&fdset);
        FD_SET(fd, &fdset);
    
        ret = select(fd + 1, &fdset, NULL, NULL, &timedelay);
        if (ret > 0) {
            while (bytes < len) {
                int rc = recv(fd, &buf[bytes], (size_t)(len - bytes), 0);
     //         printf("[%s|%s|%d]fd = %d, rc = %d, errno=%d(%s)\n", __FILE__,__func__,__LINE__, fd, rc,errno, strerror(errno));
                if (rc == -1) {
                    if (errno != EAGAIN && errno != EWOULDBLOCK) {
                        bytes = -1;
                    }
                    break;
                } else if (rc == 0) {
                    bytes = 0;
                    break;
                } else {
                    bytes += rc;
                }
            }
        }
    
        return bytes;
    #endif
    }
  2. third_party/iot_link/network/dtls/mbedtls/mbedtls_port/dtls_interface.c

    在文件顶部添加打印函数定义以及添加mbedtls_calloc以及mbedtls_free的定义,否则编译会提示错误:

    cpp 复制代码
    #define MBEDTLS_LOG LINK_LOG_DEBUG
    #ifndef mbedtls_calloc
    #define mbedtls_calloc  calloc
    #endif
    #ifndef mbedtls_free
    #define mbedtls_free  free
    #endif

    系统部分mbedtls接口不一致,固需要注释部分接口代码:

    cpp 复制代码
    mbedtls_ssl_context dtls_ssl_new(dtls_establish_info_s *info, char plat_type)
    {
    	...
    	if (info->psk_or_cert == VERIFY_WITH_PSK)
        {
    /*
            if ((ret = mbedtls_ssl_conf_psk(conf,
                                            info->v.p.psk,
                                            info->v.p.psk_len,
                                            info->v.p.psk_identity,
                                            strlen((const char *)info->v.p.psk_identity))) != 0)
            {
                MBEDTLS_LOG("mbedtls_ssl_conf_psk failed: -0x%x", -ret);
                goto exit_fail;
            }
    */
        }
        ...
    }
    
    int dtls_shakehand(mbedtls_ssl_context *ssl, const dtls_shakehand_info_s *info)
    {
    	...
    	if (MBEDTLS_SSL_IS_CLIENT == info->client_or_server)
        {
            ret = mbedtls_net_connect(server_fd, info->u.c.host, info->u.c.port, info->udp_or_tcp);
            if( 0 != ret)
            {
                ret = MBEDTLS_ERR_NET_CONNECT_FAILED;
                goto exit_fail;
            }
        }
        else
        {
            //server_fd = (mbedtls_net_context*)atiny_net_bind(NULL, info->u.s.local_port, MBEDTLS_NET_PROTO_UDP);
            ///< --TODO ,not implement yet
        }
    	...
    }
    
    void dtls_init(void)
    {
        (void)mbedtls_platform_set_calloc_free(calloc, free);
        (void)mbedtls_platform_set_snprintf(snprintf);
    //    (void)mbedtls_platform_set_printf(printf);
    }

    在iot_link/network/dtls/mbedtls/mbedtls_port/mbed_port.c文件中的dtls_imp_init()函数中,也需要注释掉未实现的接口,否则编译报错:

    cpp 复制代码
    int dtls_imp_init(void)
    {
        int ret =-1;
    
        // (void)mbedtls_platform_set_calloc_free(calloc, free); 
        // (void)mbedtls_platform_set_snprintf(snprintf);
        // (void)mbedtls_platform_set_printf(printf);
        ret = dtls_al_install(&s_mbedtls_io);
    
        return ret;
    }
  3. 在文件iot_link/network/mqtt/paho_mqtt/port/paho_osdepends.c中添加对应timersub和timeradd的实现(系统中未实现该函数):

    cpp 复制代码
    // add this for "timersub" && "timeradd"
    #ifndef	timersub
    #define timersub(s,t,a) (void) ( (a)->tv_sec = (s)->tv_sec - (t)->tv_sec, \
            ((a)->tv_usec = (s)->tv_usec - (t)->tv_usec) < 0 && \
            ((a)->tv_usec += 1000000, (a)->tv_sec--) )
    #endif
    #ifndef	timeradd
    #define timeradd(s,t,a) (void) ( (a)->tv_sec = (s)->tv_sec + (t)->tv_sec, \
            ((a)->tv_usec = (s)->tv_usec + (t)->tv_usec) >= 1000000 && \
            ((a)->tv_usec -= 1000000, (a)->tv_sec++) )
    #endif
  4. 编译中会有部分头文件提示找不到,这个时候直接将其注释即可(iot_link/network/mqtt/paho_mqtt/port/paho_osdepends.h):

    cpp 复制代码
    #define INVALID_SOCKET SOCKET_ERROR
    // #include <sys/socket.h>
    #include <sys/param.h>
    #include <sys/time.h>
    // #include <netinet/in.h>
    // #include <netinet/tcp.h>
    // #include <arpa/inet.h>
    // #include <netdb.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <string.h>
    #include <stdlib.h>
    #endif
    
    #if defined(WIN32)
    #include <Iphlpapi.h>
    #else
    // #include <sys/ioctl.h>
    // #include <net/if.h>
    #endif
  5. 因为弱引用导致无法链接相关符号,因此需要注释以下几个文件中的弱引用。

    cpp 复制代码
    文件一 third_party/iot_link/network/dtls/dtls_al/dtls_al.c
    #if 0
    __attribute__((weak))  int dtls_imp_init(void)
    {
        LINK_LOG_DEBUG("%s:###please implement dtls by yourself####",__FUNCTION__);
        return -1;
    }
    #endif
    extern int dtls_imp_init(void);
    
    文件二 third_party/iot_link/network/mqtt/mqtt_al/mqtt_al.c
    #if 0
    __attribute__((weak))  int mqtt_imp_init(void)
    {
        LINK_LOG_DEBUG("%s:###please implement mqtt by yourself####",__FUNCTION__);
        return -1;
    }
    #endif
    extern int mqtt_imp_init(void);
    
    文件三 third_party/iot_link/oc_mqtt/oc_mqtt_al/oc_mqtt_al.c
    #if 0
    __attribute__ ((weak)) int oc_mqtt_imp_init(void)
    {
        LINK_LOG_DEBUG("%s:###please implement oc mqtt by yourself####",__FUNCTION__);
        return 0;
    }
    
    __attribute__ ((weak)) int oc_mqtt_demo_main(void)
    {
        LINK_LOG_WARN("Please implement the oc mqtt v5 demo yourself");
        return -1;
    }
    #endif
    extern int oc_mqtt_demo_main(void);
  • 修改GPIO查找方式

因为GPIO框架修改了设备驱动注册的管脚号,导致应用无法根据HCS的引脚操作对应的GPIO,此问题已经提issue,如果该问题已解决,可以忽略此步骤。

打开drivers/framework/support/platform/src/gpio/gpio_manager.c,将cntlr->start = start;注释即可。

cpp 复制代码
static int32_t GpioManagerAdd(struct PlatformManager *manager, struct PlatformDevice *device)
{
    uint16_t start;
    struct GpioCntlr *cntlr = CONTAINER_OF(device, struct GpioCntlr, device);

    if ((start = GpioCntlrQueryStart(cntlr, &manager->devices)) >= GPIO_NUM_MAX) {
        PLAT_LOGE("GpioCntlrAdd: query range for start:%d fail:%d", cntlr->start, start);
        return HDF_ERR_INVALID_PARAM;
    }

//    cntlr->start = start;
    DListInsertTail(&device->node, &manager->devices);
    PLAT_LOGI("%s: start:%u count:%u", __func__, cntlr->start, cntlr->count);
    return HDF_SUCCESS;
}
  • 将对应的驱动文件复制到drvier对应目录:

    因为主仓代码中未将对应的驱动文件合并到driver/adpater/platform对应的目录下,固需要手动将文件拷贝到对应目录。若主仓已合入,可忽略此步骤。

    cpp 复制代码
    // 拷贝gpio驱动
    cp -af device/soc/allwinner/xradio/drivers/gpio/gpio_xradio.* driver/adpater/platform/gpio
    
    // 修改driver/adpater/platform/gpio/BUILD.gn文件,加上gpio_xradio的编译
    
    hdf_driver(module_name) {
      sources = []
      if (defined(LOSCFG_SOC_COMPANY_BESTECHNIC)) {
        sources += [ "gpio_bes.c" ]
      }
    
      if (defined(LOSCFG_SOC_COMPANY_ALLWINNER)) {
        sources += [ "gpio_xradio.c" ]
      }
    
      include_dirs = [ "." ]
    }

    为了节省ram资源,可以把无用的资源先关闭,如关闭内部codec,将 device/soc/allwinner/xradio/xr806/project/prj_config.h中的PRJCONF_INTERNAL_SOUNDCARD_EN设置为0,如下:

    cpp 复制代码
    /* Xradio internal codec sound card enable/disable */
    #define PRJCONF_INTERNAL_SOUNDCARD_EN   0
4、代码编译
cpp 复制代码
#首先可以查看一下hb的版本,如果hb版本为0.4.4版本就不需要更新。

```
## 查看hb版本
hb --version

## 更新hb, 以下指令需要在openharmony SDK根目录执行
pip3 uninstall ohos_build
pip3 install build/lite
```

编译命令:
hb set  // 如果是第一次编译,Input code path 命令行中键入"./" 指定OpenHarmony工程编译根目录后 回车,
如下图所示,使用键盘上下键选中wifi_skylark
复制代码
cpp 复制代码
hb build // 如果需要全量编译,可以添加-f 选项

生成的固件保存在out/xradio/smart_weight_scale目录下

5、固件烧录

参照文档:XR806快速上手指导文档

6、设备配网
  1. 在设备上电前需准备好安装了数字管家应用的HarmonyOS手机, 并在设置中开启手机的NFC功能;
  2. 写设备NFC标签;
  3. 烧录完成后,上电。开发者在观察开发板上状态LED灯以8Hz的频率闪烁时,将手机上半部靠近开发板NFC标签处(无NFC标签的可用NFC贴纸替代);
  4. 碰一碰后手机将自动拉起数字管家应用并进入配网状态;
  5. 配网过程中需要 连接设备的AP热点,然后填写需要配置的wifi的密码;
  6. 最后点击配置,手机会将ssid以及对应的密码通过AP热点发送到设备。

鸿蒙Next核心技术分享

1、鸿蒙基础知识←《鸿蒙NEXT星河版开发学习文档》

2、鸿蒙ArkUI←《鸿蒙NEXT星河版开发学习文档》

3、鸿蒙进阶技术←《鸿蒙NEXT星河版开发学习文档》

4、鸿蒙就业高级技能←《鸿蒙NEXT星河版开发学习文档》

5、鸿蒙多媒体技术←《鸿蒙NEXT星河版开发学习文档》

6、鸿蒙南向驱动开发←《鸿蒙NEXT星河版开发学习文档》

7、鸿蒙南向内核设备开发←《鸿蒙NEXT星河版开发学习文档》

8、鸿蒙系统裁剪与移植←《鸿蒙NEXT星河版开发学习文档》

相关推荐
国服第二切图仔15 小时前
HarmonyOS APP《画伴梦工厂》开发第44篇-页面路由注册与动态加载——main_pages配置
深度学习·华为·harmonyos
我是小a17 小时前
「财务管家」智能记账应用:HarmonyOS NEXT 页面布局设计与实现详解
华为·harmonyos·鸿蒙·鸿蒙系统
爱勇宝17 小时前
办公资料反复修改、补传、交接混乱,我做了个桌面工具来解决这件事
前端·后端·程序员
SuperHeroWu717 小时前
HarmonyOS7特性解读
harmonyos·特性·智能化·多窗形态·场景协同·空间化
庄周de蝴蝶17 小时前
梅开二度,这次拿下系统分析师
后端·程序员
心中有国也有家19 小时前
使用 DevEco Studio 配置 Flutter 鸿蒙签名
学习·flutter·华为·harmonyos
如此风景20 小时前
鸿蒙开发状态管理 V2 全部装饰器详解速查表
harmonyos
Hilaku20 小时前
为什么你的大文件断点续传,一遇到弱网就崩溃?
前端·javascript·程序员
爱吃大芒果20 小时前
鸿蒙 ArkTS 网络工程进阶:@ohos.net.http 模块下的长连接、超时与防内存泄漏实战
华为·harmonyos
FrameNotWork20 小时前
HarmonyOS 6.0 沉浸式状态栏与安全区域适配实战
华为·harmonyos