-
中奖结果公示感谢各位小伙伴参与本次活动,欢迎关注华为云开发者社区技术直播更多活动~本次活动获奖名单如下(部分抽奖未填问卷用户无账号名):账号名 奖项名称 奖品名称 备注持久观看有奖开发者定制帆布袋持久观看有奖开发者定制帆布袋持久观看有奖华为云云宝手办-盲盒款持久观看有奖华为云云宝手办-盲盒款
-
参考书籍资料:《鸿蒙操作系统设计原理与架构》 (李毅,任革林)布道活动地点:东北大学(浑南校区)
-
鸿蒙版cordova/PhoneGap介绍鸿蒙版cordova或PhoneGap,这里统称为鸿蒙版Cordova,遵守cordova官方,不再使用鸿蒙版PhoneGap了。 cordova移动端跨平台研发的重要框架之一,支持Android和Ios,官方并不支持鸿蒙系统,harmony-cordova是鸿蒙的cordova,这里主要介绍鸿蒙版corddova,安卓和Ios请查看cordova官方文档,cordova除了cordova sdk外,还有很多的插件,开发者根据自己的APP的需要可以选择使用相关的插件,鸿蒙版cordova sdk地址鸿蒙版cordova已经上架鸿蒙三方库中心,开发者可以直接在DevEco studio中直接使用。cordova sdk 三方库地址:https://ohpm.openharmony.cn/#/cn/detail/harmony-cordova鸿蒙版cordova支持的插件插件ID接口地址说明cordova-plugin-whitelistcid:link_12白名单cordova-plugin-network-informationcid:link_0网络管理cordova-plugin-inappbrowsercid:link_8内置浏览器cordova-sqlite-storagecid:link_11嵌入式数据库cordova-plugin-cameracid:link_14相机功能cordova-plugin-filecid:link_15本地文件管理cordova-plugin-file-transfercid:link_5上传和下载文件cordova-plugin-dialogscid:link_13弹窗cordova-plugin-datepickercid:link_1日期选择phonegap-plugin-barcodescannercid:link_3扫码cordova-plugin-media-capturecid:link_6视频录制cordova-plugin-alipay-v2调用接口:cid:link_9依赖插件:https://ohpm.openharmony.cn/#/cn/detail/@cashier_alipay%2Fcashiersdk自定义支付宝支付插件,只是按照文档接口调用了支付宝插件依赖支付宝官方插件aliyun.uploadvod暂无文档,需联系开发者阿里云OSS插件,实现移动端直接上传文件到阿里云OSScordova-plugin-android-permissionscid:link_2授权插件,接口永远返回为拥有权限,主要为兼容android移植到harmony使用的,避免修改Android端的代码,harmong授权已下放到各个插件,无需单独编写代码phonegap-bluetooth-plugincid:link_10经典蓝牙插件cordova-plugin-ble-centralcid:link_10低功耗蓝牙插件cordova-base64-to-gallerycid:link_7保存图片到相册cordova-plugin-huawei-pushcid:link_4华为推送,自定义插件,详细使用请咨询开发者Android移植鸿蒙步骤1,打开DevEco创建项目,选择Empty Ability进入下一步,填写必要信息,这里要注意,bundle name 先填写com.example.myapplication,也就是保持默认不变,因为在没有cordova.crt证书的情况下,cordova鸿蒙版要求bundle name必须为com.example.myapplication,主要用于研发测试,如果开发测试完成要修改bundle name上架鸿蒙应用市场,请联系开发者申请cordova.ert证书,或者事先联系开发者提供技术服务。2,项目创建成功后,复制原有Android studio的工程assests/www目录下面的所有文件到鸿蒙工程entry/src/main/resources/目录下,注意直接复制原andriod工程www目录下的文件,不包含www。3,复制原android工程res/xml目录下的config.xml文件到鸿蒙工程entry/src/main/resources/目录下。4,打开DevEco studio的Terminal终端,进入工程目录,执行 ohpm install harmony-cordova 安装本插件。5,打开鸿蒙工程文件entry/src/main/etx/pages/Index.ets文件,修改代码如下:import { MainPage, pageBackPress, pageHideEvent, pageShowEvent } from 'harmony-cordova/Index'; @Entry @Component struct Index { onPageShow(){ pageShowEvent(); //页面显示通知cordova } onBackPress() { pageBackPress(); //拦截返回键由cordova处理 return true; } onPageHide() { pageHideEvent(); //页面隐藏通知cordova } build() { RelativeContainer() { MainPage(); //webview首页index.html } .height('100%') .width('100%') } }6,打开鸿蒙工程文件/entry/src/main/ets/entryAbility/EntryAbility.ets文件,修改onCreate函数如下import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { window } from '@kit.ArkUI'; import { webview } from '@kit.ArkWeb'; import { setSchemeHandler } from 'harmony-cordova/Index';...onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {webview.WebviewController.initializeWebEngine();//webview引擎初始化setSchemeHandler();//设置webview schemehilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');}7,鸿蒙混合研发,也许您会增加其他page页面,不一定应用的首页为cordova webview(index.html)的首页,例如应用增加了鸿蒙的原生的启动页面,包含首页弹窗,同意隐私政策后,然后再从启动页面进入cordova的页面,这样避免在用户没有同意隐私政策的情况下,初始化cordova sdk,因为初始化cordova sdk,系统读取了设备的网络状态,因为国内相关法律规定,在用户没有同意隐私政策的情况下,不允许读取设备的网络标识。8,做以上代码修改后,鸿蒙的移植已经完毕,可以使用模拟器或者真机进行编译和测试了。Ios移植鸿蒙步骤如果您的项目有android和Ios的工程,请参考android项目移植项目的鸿蒙下,如果您的项目没有andriod工程,只有Ios工程,请使用如下方法移植,移植时大部分内容和安卓一样,只是复制的文件的路径不一致,以下只介绍不同部分,相同部分请参考android移植步骤。1,复制Xcode的Ios工程目录下的Staging/www目录下的所有文件到鸿蒙工程entry/src/main/resources/目录下。 2,Xcode工程的config.xml文件在Staging目录下,Xcode工程的该文件不能直接被鸿蒙版cordova使用,需要进行转换,该文件主要记录的是插件的名称和初始化的类,因为鸿蒙版是根据android的config.xml进行插件初始化的,因此需要将Xcode工程config.xml转为安卓的config.xml,请将Xcode工程使用node加入安卓平台,系统会自动生成android版的config.xml。然后将文件复制到鸿蒙版工程的entry/src/main/resources/下。附加说明:本人认为使用cordoca跨平台研发,一般至少都会包含android和ios两大平台,很少只有ios平台,没有android平台的,所以大部分移植鸿蒙参考android移植步骤,后续升级SDK会兼容Ios工程的config.xml,无需转换就可以使用。新项目,一次开发适用于andriod、Ios和Harmony三大平台由于cordova官方当前并不支持HarmonyOS平台,使用node无法直接将HarmonyOS加入到cordova,也无法直接安装插件到HarmonyOS,因此对于新项目要一次开发满足三大平台的话,建议先通过node加入Android和Ios平台和安装插件,后续研发可以使用Android studio研发和调试,待研发成功后,然后再在Xcode和DevEco做跨平台适配。Xcode适配请参考cordova的官方文档,HarmonyOS适配请参考以上Android的移植步骤。特别说明当前版本不支持使用者自定义插件研发,如果该版本没有包含您要使用的插件,或者您的项目中有Android或Ios的自定义插件,需要移植到HarmonyOS平台,请您和本开发者联系,获取技术支持。使用鸿蒙版cordova sdk在开发测试阶段务必将bundle name修改为com.example.myapplication,如果将bunlde name改为正式的Id,鸿蒙版cordova sdk会读取entry/src/main/resources/目录的cordova.crt证书文件,用于验签,如果该文件不存在,启动应用后,应用会闪退。如果应用的bundle name为com.example.myapplication,鸿蒙版 cordova sdk会跳过验签,不检测cordova.crt文件。但是上架鸿蒙应用市场,必须将bundle name改为正式的id,所以请联系开发者申请cordova.ert证书,另外由于操作系统之间的差异,虽然保持了cordova的插件接口不变,但是返回值会有所调整,后续文档会逐步完善,在使用本插件跨平台研发时请联系开发者提供技术服务。
-
2024年7月23日,在学生自发组织,负责教师指导下,兰州大学信息科学与工程学院(下文简称“信息学院”)网络安全专业暑期学校暨华为布道师活动“OpenHarmony网络安全能力综合培训”圆满举行。本次活动由华为开发者学生布道师王天一、聂嘉一同学自发组织,信息学院刘新副教授指导,并邀请信息学院博士生刘子昂作分享报告。本次活动主要覆盖了OpenHarmony生态发展、开源社区简介、网络漏洞挖掘等内容。活动现场照片活动伊始,聂嘉一同学介绍了OpenHarmony操作系统的设计理念与生态发展,同时分享了OpenHarmony设备兼容性测试相关经验、王天一同学为大家讲解了近期开源工作的内容,并介绍了兰州大学OpenHarmony俱乐部所承接的年度课题。聂嘉一同学为大家介绍OpenHarmony兼容性测试流程王天一同学为大家介绍OpenHarmony年度课题上午的第二个环节,刘新副教授分享自己在HDC2024发现活动漏洞的经验,并以多所高校官网中存在的安全隐患为例,讲解了常见网络安全漏洞的类型,为同学们下午的实战演练提供理论基础。刘新副教授为大家分享HDC2024漏洞挖掘经验在上午活动的尾声,刘子昂博士作开源许可证治理的报告,揭示了不当使用许可证带来的潜在风险,并介绍许可证兼容性分析的方法。刘子昂博士为大家讲解开源许可证治理23日下午,刘新老师与几位同学指导大家在华为云服务器靶场中进行漏洞挖掘,让大家在实践中提升自身的专业能力。现场挖掘靶场安全漏洞背景概述:华为布道师计划旨在培养更多能够掌握和使用华为开放技术评聘专业人才,更为致力于引领、指导和支持他人学习和应用这些技术。兰州大学信息科学与工程学院周庆国教授为首批华为教师布道师。
-
2024年7月13日-7月18日,在学生自发组织,负责教师指导下,兰州大学信息科学与工程学院(后简称“信息学院”)“人工智能专业综合实训”暑期学校暨华为开发者布道师活动“OpenHarmony开发实战提升”圆满举行。本次活动由华为开发者学生布道师王天一、聂嘉一同学自发组织,信息学院教师狄长艳老师指导,同时华为开发者教师布道师、信息学院教师周庆国教授,信息学院教师刘传义副教授等受邀做了分享报告。本次活动主要覆盖了OpenHarmony的南北向开发以及OpenHarmony初级人才认证考试等内容。活动现场照片活动首日,聂嘉一同学介绍了OpenHarmony操作系统的设计理念与生态发展、钱浩莱同学详细讲解了鸿蒙南北向开发流程,并重点介绍了鸿蒙应用程序的开发。聂嘉一同学为大家介绍OpenHarmony生态发展前景钱浩莱同学为大家讲解OpenHarmony应用程序开发实例此外,活动中王天一同学为大家讲解了OpenHarmony初级人才认证的基本信息和考试方法,在讲解完成之后,还组织了大家现场进行OpenHarmony人才认证考试。王天一同学为大家讲解OpenHarmony人才认证相关内容经过了一个小时的考试之后,共有36名同学通过了OpenHarmony初级人才认证,周庆国教授为同学们颁发了获奖证书。周庆国教授为获得OpenHarmony初级人才认证的同学颁发证书在为期六天的暑期学校中,几位同学均在实战环节指导大家动手实践,从零开始完成智能设备的开发。在活动的最后一日,参加活动的同学们共同合影留念。获得OpenHarmony初级人才认证的同学们合影留念背景概述:华为开发者布道师计划旨在培养更多能够掌握和使用华为开放技术评聘专业人才,更为致力于引领、指导和支持他人学习和应用这些技术。其中,信息学院周庆国教授为首批华为开发者教师布道师。
-
我在Index页面上面这样放置,登录的时候修改了this.token,按文档意思应该是会自动持久化的,但当我关闭app重启打开,又显示token没有数据.试了很多次了,不知道是不是我用arkUI-X的缘故?环境是DevEco Studio 4.0 Release Build Version: 4.0.0.600, built on October 17, 2023ArkUI-X 1.0.0.0OpenHarmony SDK API 10ace build apk 生成的apk文件放在安卓手机安装的,因为我的DevEco Studio经常识别不到手机.
-
润和大禹系列HH-SCDAYU200是润和软件推出的社区内首款支持OpenHarmony富设备的开发板,基于瑞芯微RK3568,集成双核心架构GPU以及高效能NPU,板载四核64位Cortex-A55 处理器采用22nm先进工艺,主频高达2.0GHz,支持蓝牙、Wi-Fi、音频、视频和摄像头等功能,拥有丰富的扩展接口,支持多种视频输入输出接口,配置双千兆自适应RJ45以太网口,可满足NVR、工业网关等多网口产品需求。 目前DAYU200已经面向行业和开发者全面供货,即刻下单! 淘宝:cid:link_0DAYU200亮点 样例:基于DAYU200的分布式音乐播放器本样例为基于DAYU200的分布式音乐播放器,实现了基本的音乐播放、暂停、上一曲、下一曲功能,并使用分布式能力完成了音乐播放状态的跨设备迁移。 代码仓库cid:link_1实现功能1:音乐播放使用MediaLibrary完成本地媒体文件扫描,并通过AudioPlayer完成了音乐的播放。实现功能2:跨设备迁移播放使用DeviceManager完成了分布式设备列表的显示。使用分布式调度以及分布式数据完成了跨设备迁移功能。【运行步骤】编译运行:参考DevEco Studio(OpenHarmony)使用指南搭建OpenHarmony应用开发环境、并导入本工程进行编译、运行。运行结果截图: 【分布式流转体验】 硬件准备:准备两台润和DAYU200开发板,并通过网线直连下载这个临时触发的构建版本并烧录进两台开发板若下载地址过期,可以参考这个临时PR,自行提交PR并start build触发构建 也可以搭建标准系统源码环境,按device_manager仓库首页指导修改PIN_CODE以及PORT后,执行./build.sh --product-name rk3568编译版本后进行烧录开发板1配置一个IP(每次重启后需要重新配置)hdc shell ifconfig eth0 192.168.1.222 netmask 255.255.255.0开发板2配置另外一个不一样的IP(每次重启后需要重新配置)hdc shell ifconfig eth0 192.168.1.111 netmask 255.255.255.0打开音乐,点击左下角流转按钮,列表中会出现远端设备的id,选择远端设备id即可实现跨设备迁移播放
-
小熊派烧录过程中执行upload出现以下错误应该如何解决?
-
本示例将演示如何利用启航KP_IOT主控板和智能风扇模块进行案例开发。模块介绍智能风扇模块主要的部件有STH30温湿度传感器,一个红外传感器,一个led灯,一个按键和电机,该模块能够实现按键控制电机的启停,电机启动一共分三档,按一次按键增加一个档位,控制电机这一部分用到了pwm,和gpio的知识点。该模块上的STH30传感器可以监控环境温湿度,STH30是通过i2c进行数据交互的,采集的数据还可以显示在oled屏上,oled屏是通过spi进行数据交互。模块上的红外传感器能够实现对物体的检测,当红外传感器检测到物体时led灯会被点亮。智能风扇模块主要试验步骤我们将调用motor_demo()函数,我们就可以在motor_module.c文件中motor_demo()函数声明中完成电机的控制功能,电机控制需要用到pwm,按键需要用到一个gpio,第一步先对io口进行复用,像i2c,pwm,spi,uart等这样功能性引脚复用是在wifiiot/init/app_io_init.c文件中完成的。查看电路原理图和芯片手册中可以知道电机是使用的pwm2,知道电机使用的是pwm2之后,就要知道pwm2是哪个gpio引脚输出的,在include/hi_io.h中可以查看,每个gpio引脚可以复用的功能。gpio2管能够复用成gpio,uart1_rts,spi,pwm2_out等功能,我们需要用到的是pwm2_out,所以在wifiiot/init/app_io_init.c中将gpio_2复用成pwm2_out功能,设置引脚功能的函数hi_u32hi_io_set_func(hi_io_name id, hi_u8 val)的具体功能介绍可以在include/hi_io.h中可以查看。pwm设置完成之后在motor_module.c文件中motor_gpio_io_init()函数中对按键接入的引脚进行复用,从原理图可看到按键KEY-1是接GPIO_05。步骤1 按键对应io5之后在motor_gpio_io_init()中将io5复用成gpio/*gpio5按键控制电机速度*/ ret = hi_io_set_func(HI_IO_NAME_GPIO_5, HI_IO_FUNC_GPIO_5_GPIO); if (ret != HI_ERR_SUCCESS) { printf("===== ERROR ===== gpio -> hi_io_set_func ret:%d\r\n", ret); return; } printf("----- gpio5 fan set func success-----\r\n"); ret = hi_gpio_set_dir(HI_GPIO_IDX_5, HI_GPIO_DIR_IN); if (ret != HI_ERR_SUCCESS) { printf("===== ERROR ===== gpio -> hi_gpio_set_dir1 ret:%d\r\n", ret); return; } printf("----- gpio set dir success! -----\r\n");io设置完成之后,就可以进行功能的编写了,我们想要实现的功能是通过按键控制电机的启停,电机有三个档位,按键按一次增加一个档位,电机处于三档时再按一次按键电机将停止。首先我们要创建一个任务去实时监听按键接入io引脚的状态,OpenHarmony系统中任务的创建调用hi_u32hi_task_create(hi_u32 *taskid, const hi_task_attr attr,hi_void (*task_route)(hi_void *), hi_void *arg);步骤2 创建的电机任务的属性,包括任务优先级,任务栈的大小,任务名,在任务处理函数中去实现我们想要的功能//创建的电机任务的属性,包括任务优先级,任务栈的大小,任务名等 static unsigned int g_MonitorTask; const hi_task_attr MonitorTaskAttr = { .task_prio = 20, //优先级范围20~30之间 .stack_size = 4096, //任务大小 .task_name = "BuggyNetworkMonitorTask",//任务名称,可自行修改 }; void *MonitorOledTask(void * para) /* OLEDtask处理函数 */ { while(1){ test_led_screen(); printf("OLED task \r\n"); } return NULL; } // 电机task处理函数 void *MonitorMotorTask(void * para) { while(1){ gpio_getval(); //主要任务,对电机的控制在此函数中实现 infrared_ctrl(); //printf("fan task \r\n"); } return NULL; }步骤3 在gpio_getval()实现对电机控制;//按键控制电机程序 hi_void gpio_getval(hi_void) { hi_u32 ret; int temp; static int key = 0; //将gpio_val_1置为1,置为1是因为从原理图中可知按键默认是高电平,按下为低电平 hi_gpio_value gpio_val_1 = HI_GPIO_VALUE1; temp = infrared_ctrl(); //获取gpio5引脚电平,并赋值给gpio_val_1 ret = hi_gpio_get_input_val(HI_GPIO_IDX_5, &gpio_val_1); if (ret != HI_ERR_SUCCESS) { printf("===== ERROR ===== gpio -> hi_gpio_get_input_val ret:%d\r\n", ret); return; } //printf("----- gpio input val is:%d. -----\r\n", gpio_val_1); //当gpio_val_1为低电平是说明按下了按键,按1次按键增加一个档位 if(gpio_val_1 == 0){ //休眠1s是为了消除按键的抖动 sleep(1); if(gpio_val_1 == 0){ key++; } //key表示是几档,根据不同的档位再输出不同占空比的pwm波去控制电机 switch(key){ case 0: break; case 1: //电机1档,控制端口2输出1占空比的pwm波 motor_pwm_start(1); break; case 2: //电机2档,控制端口2输出2占空比的pwm波 motor_pwm_start(2); break; case 3: //电机3档,控制端口2输出3占空比的pwm波 motor_pwm_start(3); break; default: printf("invalid mode \r\n"); } //当key大于4或等于0时停止电机 if(key >= 4 || key == 0 || temp == 1){ key = 0; ret = hi_pwm_stop(HI_PWM_PORT_PWM2); if(ret != 0){ printf("hi_pwm_stop failed \r\n"); } } } //printf("key : %d \r\n",key); }pwm波控制电机输出部分程序:在使用pwm之前需要先对pwm进行初始化,初始化pwm只需要调用hi_pwm_init(parm),函数中的参数(parm)是需要初始化的端口,使用的pwm2需要填宏定义HI_PWM_PORT_PWM2,具体宏定义含义在include/hi_pwm.h中有说明。步骤4 对端口的初始化只需要完成一次,所以在创建电机任务之前调用一次motor_pwm_init()即可hi_void motor_pwm_init(hi_void) { int ret = -1; ret = hi_pwm_deinit(HI_PWM_PORT_PWM2); //初始化端口2 if(ret != 0){ printf("hi_pwm_deinit failed :%#x \r\n",ret); } ret = hi_pwm_init(HI_PWM_PORT_PWM2); //初始化端口2 if(ret != 0){ printf("hi_pwm_init failed :%#x \r\n",ret); } ret = hi_pwm_set_clock(PWM_CLK_160M); //设置端口2的时钟源频率 if(ret != 0){ printf("hi_pwm_set_clock failed ret : %#x \r\n",ret); } }对电机速度的控制实际上就是控制pwm的占空比和频率,想要输出不同占空比的pwm波调用函数hi_u32hi_pwm_start(hi_pwm_port port, hi_u16 duty, hi_u16freq);参数port表示端口号,duty占空比值,freq频率。在我们使用的模块中,时钟频率默认是160000000hz,所以可以定义一个宏去表示时钟频率,这里我们是用PWM_CLK_FREQ表示时钟频率,分频倍数165535,频率范围就是2441160000000(频率=时钟源频率/分频倍数),我们用最低频率就可以了,所以将频率用一个宏freq去表示值为2441。步骤5 motor_pwm_start(unsigned int duty)中duty就表示占空比,想让pwm2端口输出多少占空比的波形,直接在调用该函数时传入占空比值就可以了。hi_void motor_pwm_start(unsigned int duty) { int ret = 0; DBG("motor start \r\n"); if(duty == 0){ ret = hi_pwm_stop(HI_PWM_PORT_PWM2); //停止pwm2端口输出 if(ret != 0){ printf("hi_pwm_start failed ret : %#x \r\n",ret); } } ret = hi_pwm_start(HI_PWM_PORT_PWM2, duty*(PWM_CLK_FREQ/freq)/100, PWM_CLK_FREQ/freq); //输出duty占空比的pwm波 if(ret != 0){ printf("hi_pwm_start failed ret : %#x \r\n",ret); } }步骤6 motor_pwm_start(unsigned int duty)中duty就表示占空比,想让pwm2端口输出多少占空比的波形,直接在调用该函数时传入占空比值就可以了。hi_void motor_demo(hi_void) { int ret; motor_gpio_io_init(); //完成对按键所接io的复用 motor_pwm_init(); //对pwm进行初始化 //创建一个任务去专门处理电机控制任务 ret = hi_task_create(&g_MonitorTask, // task标识// &MonitorTaskAttr, MonitorMotorTask, // task处理函数 // NULL); // task处理函数参数 // if (ret < 0) { printf("Create monitor motor task failed [%d]\r\n", ret); return; } return; }红外传感器模块红外传感器有一个发射端和一个接收端,当发射端发出的电磁波被挡住返回,接收端接收到后红外传感器接到模块上的引脚就会从低电平变成高电平,所以我们可以通过监控红外传感器的引脚电平高低来判断是否检测到物体,目前实现的现象是,当检测到物体时led就会被点亮,没检测到物体led就灭。写程序之前我们要先确定红外传感器接入的引脚和led接入的引脚,从原理图中可以看到红外传感器LED_infrared接入的引脚是GPIO_07,LED_SW1灯接入的引脚是GPIO_06。步骤1 知道红外传感器和led接入的引脚后,就需要对相应的IO进行复用,因为是同一个模块所以可以在motor_gpio_io_init()中实现io7,io8的复用。//设置io8的方向,因为led灯是输出信号,所以设置成out ret = hi_gpio_set_dir(HI_GPIO_IDX_8, HI_GPIO_DIR_OUT); if (ret != HI_ERR_SUCCESS) { printf("===== ERROR ===== gpio -> hi_gpio_set_dir1 ret:%d\r\n", ret); return; } /*gpio7 电机模块红外传感*/ ret = hi_io_set_func(HI_IO_NAME_GPIO_7, HI_IO_FUNC_GPIO_7_GPIO); if (ret != HI_ERR_SUCCESS) { printf("===== ERROR ===== gpio -> hi_io_set_func ret:%d\r\n", ret); return; } printf("----- io set func success-----\r\n"); //设置io7的方向,因为红外传感器是输入信号,所以设置成输入 ret = hi_gpio_set_dir(HI_GPIO_IDX_7, HI_GPIO_DIR_IN); if (ret != HI_ERR_SUCCESS) { printf("===== ERROR ===== gpio -> hi_gpio_set_dir1 ret:%d\r\n", ret); return; } printf("----- gpio set dir success! -----\r\n");步骤2对io功能复用完成后,就可以完成对led控制这一部分的功能了,首先这两引脚的默认值都是低电平,所以先将这两个引脚赋值HI_GPIO_VALUE0,然后根据gpio_val_7引脚的电平高低来判断红外传感器是否检测到物体,检测到物体是将gpio_val_8置为高电平点亮led。hi_void infrared_ctrl(hi_void) { hi_u32 ret; hi_gpio_value gpio_val_7 = HI_GPIO_VALUE0; //设置gpio_val_7默认值 hi_gpio_value gpio_val_8 = HI_GPIO_VALUE0; //设置gpio_val_8默认值 ret = hi_gpio_get_input_val(HI_GPIO_IDX_7, &gpio_val_7); //监控gpio_val_7的电平 if (ret != HI_ERR_SUCCESS) { printf("===== ERROR ===== gpio -> hi_gpio_get_input_val ret:%d\r\n", ret); return; } //printf("----- gpio input val is:%d. -----\r\n", gpio_val_7); if(gpio_val_7 == 1){ hi_gpio_set_ouput_val(HI_GPIO_IDX_8,HI_GPIO_VALUE1); //gpio_val_7为高电平时,将HI_GPIO_IDX_8置为高电平输出 }else{ hi_gpio_set_ouput_val(HI_GPIO_IDX_8,HI_GPIO_VALUE0);//gpio_val_7为低电平时,将HI_GPIO_IDX_8置为低电平输出 } }步骤3 功能完成后就需要在适合的时机去调用,因为红外线监控也是需要实时监控gpio引脚,和监控按键一样所以可以在电机任务中去调用。void *MonitorMotorTask(void * para) /* 电机task处理函数 */ { while(1){ gpio_getval(); //电机按键监控 infrared_ctrl(); //红外传感器监控 } return NULL; }sht3x温湿度传感器模块SHT3x-DIS是Sensirion新一代的温湿度传感器,精度为±2%RH和±0.3℃,输入电压范围从2.4V到5.5V,采用IIC总线接口,速率可达1MHz。测量温湿度范围分别为是-40℃ ~ 125℃和0 ~ 100%。具体规格和原理参考说明手册。步骤1 初始化sth3xvoid SHT3X_init(void) { int ret = 0; unsigned short data[2] = {0}; SHT3X_SoftReset(); //软件复位SHT3X SHT3x_WriteCMD(CMD_READ_SERIALNBR); //向i2c发送读命令 SHT3x_WriteCMD(CMD_MEAS_PERI_2_M); //设置读取周期为2hz }步骤2 读取测量数据void SHT3X_ReadMeasurementVal(unsigned int para) { (void) para; static int cunt = 0; static float humidity = 0.0; static float temperature = 0.0; SHT3X_ReadMeasurementBuffer(&temperature,&humidity); //将读取数据存到temperature和humidity中 } //数据存储的具体实现 void SHT3X_ReadMeasurementBuffer(float* temperature, float* humidity) { unsigned int rawValueTemp = 0; SHT3x_WriteCMD(CMD_FETCH_DATA); //读取数据前先发送一个周期读取指令 SHT3x_Read4BytesDataAndCrc((unsigned short *)&rawValueTemp);//读取i2c上四个字节的数据 dump_buf((unsigned char *)&rawValueTemp,sizeof(rawValueTemp)); //调试打印读取的数据 *temperature = SHT3X_CalcTemperature(rawValueTemp); //将读取的数据转换成浮点型温度数据 *humidity = SHT3X_CalcHumidity(*((unsigned short *)(&rawValueTemp)+1));//将读取的数据转换成浮点型湿度数据 Temperature = *temperature; Humidity = *humidity; DBG("temp :%f,hum :%f \r\n",Temperature,Humidity); //打印读取的温湿度 } //将读取的数据转换成浮点型的温度 static float SHT3X_CalcTemperature(unsigned short rawValue) { return 175.0f * (float)rawValue / 65535.0f - 45.0f; //转换公式 } //将读取的数据转换成浮点型的湿度 static float SHT3X_CalcHumidity(unsigned short rawValue) { return 100.0f * (float)rawValue / 65535.0f; //转换公式 } //读取4字节数据的具体实现 int SHT3x_Read4BytesDataAndCrc(unsigned short *data) { int ret = -1; unsigned char sendbuf[2] = {0}; unsigned char rcvbuf[6] = {0}; hi_i2c_data sht3x_i2c_data = { 0 }; //i2c在该模块的数据收发数据都是存储在结构体中的,该结构体可以再include/hi_i2c.h中查看 sht3x_i2c_data.send_buf = sendbuf; sht3x_i2c_data.send_len = sizeof(sendbuf); sht3x_i2c_data.receive_buf = rcvbuf; sht3x_i2c_data.receive_len = sizeof(rcvbuf); if(data == NULL){ DBG("invalid para \r\n"); return ret; } ret = hi_i2c_read(0, ((unsigned char)0x44) << 1 | 0x01, &sht3x_i2c_data); //sht3x地址为0x44,读温湿度传感器中数据,数据存储在sht3x_i2c_data中 if(ret != 0){ DBG("hi_i2c_read failed ret :%#x \r\n",ret); return ret; } ret = SHT3X_CheckCrc(rcvbuf,2,rcvbuf[2]); //将读取的数据进行校验 if(ret != NO_ERROR){ DBG("read serial number crc check failed \r\n"); return ret; } ret = SHT3X_CheckCrc(&rcvbuf[3],2,rcvbuf[5]); //将读取的数据进行校验 if(ret != NO_ERROR){ DBG("read serial number crc check failed \r\n"); return ret; } data[0] = rcvbuf[0] << 8 | rcvbuf[1]; //将数据存到data数组中 data[1] = rcvbuf[3] << 8 | rcvbuf[4]; return 0; }步骤3 任务调用hi_void motor_demo(hi_void) { int ret; SHT3X_init(); //初始化SHT3X ret = hi_task_create(&g_MonitorTask, // task标识 // &MonitorTaskAttr, MonitorSthTask, // task处理函数 // NULL); // task处理函数参数 // if (ret < 0) { printf("Create monitor motor task failed [%d]\r\n", ret); return; } return; }到这一步温湿度传感器的程序已经编写完成了,可以将程序进行编译然后下载到模组中验证一下是否可以读取温湿度,如果能读取到温湿度,在日志中会打印出读取的数据。OLED显示模块采集完成数据之后可以在oled模块上显示,oled模块的具体开发详见oled开发流程知道,这里是直接使用oled去显示SHT3X读取的温湿度,显示温湿度首先我们要对oled进行初始化,我们使用的spi0所以初始化时spi_id=0。hi_void screen_spi_master_init(hi_spi_idx spi_id){int ret = -1;//screen_ERR;test_spi_para spi_para; //test_spi_para结构体是spi的基础属性,定义在oled_module/spi_screen.h文件中步骤1 给spi基础属性赋值spi_para.spi_id = spi_id; //spi端口号,我们是用的是0 spi_para.irq = HI_FALSE; //是否启用中断,选择否 spi_para.cfg_info.data_width = HI_SPI_CFG_DATA_WIDTH_E_8BIT; //传输数据位为8位 spi_para.cfg_info.cpha = HI_SPI_CFG_CLOCK_CPHA_0; //时钟相位0,采集第一个跳变沿数据 spi_para.cfg_info.cpol = HI_SPI_CFG_CLOCK_CPOL_0; //时钟极性0,空闲状态为低电平 spi_para.cfg_info.fram_mode = HI_SPI_CFG_FRAM_MODE_MOTOROLA; //选用的通讯协议 spi_para.cfg_info.endian = HI_SPI_CFG_ENDIAN_LITTLE; //数据传输为小段模式 spi_para.slave = HI_FALSE; //没有从机 spi_para.lb = HI_FALSE; //不设置回环测试模式 spi_para.dma_en = HI_FALSE; //不采用dma spi_para.cfg_info.freq = 2000000; //通讯频率2Mhz test_spi_printf("app_demo_spi_test_cmd_mw_sr Start"); ret = screen_spi_init(spi_para.spi_id, &(spi_para.cfg_info), spi_para.slave); //spi的系统初始化 if (ret == HI_ERR_SUCCESS) { test_spi_printf("SPI init succ!"); } else { test_spi_printf("SPI init fail! %x ", ret); return; } hi_spi_set_loop_back_mode(spi_para.spi_id, spi_para.lb); //设置回环测试模式 hi_sleep(1000); /* 1000 */ hi_spi_set_irq_mode(spi_para.spi_id, spi_para.irq); //设置中断模式 hi_spi_set_dma_mode(spi_para.spi_id, spi_para.dma_en); //设置dma模式 hi_sleep(1000); /* 1000 */ }步骤2 初始化完成之后,需要创建一个oled任务去完成显示功能ret = hi_task_create(&g_MonitorTask, // task标识 // &MonitorTaskAttr, MonitorOledTask, // task处理函数 // NULL); // task处理函数参数 // if (ret < 0) { printf("Create monitor oled task failed [%d]\r\n", ret); return; } void *MonitorOledTask(void * para) /* OLEDtask处理函数 */ { while(1){ test_led_screen(); //显示功能 printf("OLED task \r\n"); } return NULL; }温湿度的显示主要是由TEST_Menu2()显示的,这个oled模块显示数字有一个特点,它显示20这样的两位数时,是将这两位数分开来显示的,先显示2再显示0,我们读取的温度是一个浮点型数据,所以在显示时需要将各个位上的数字分离出来。void TEST_Menu2(void) { extern float Temperature; //Temperature的读取是在不同的文件中,所以想在这个文件中使用就需要将Temperature定义成全局变量,这边引用时加extern extern float Humidity; printf("Temperature:%f Humidity:%f \r\n",Temperature,Humidity); int a = 0; int b = 0; int c = 0; int d = 0; int e = 0; int f = 0; int g = 0; a = Temperature; b = a / 10; //整除获取十位上的数据 c = a % 10; //取余获取个位上的数据 d = (Temperature - a) * 10; //Temperature为浮点型数据,a为整形,相减之后就是小数,再乘十,获取到的就是小数后的第一位 e = Humidity; f = e / 10; //整除获取十位上的数据 g = e % 10; //取余获取各位上的数据 printf("b:%d c:%d d:%d f:%d g:%d\r\n",b,c,d,f,g); u8 i; //图形界面的绘制 GUI_DrawLine(0, 10, WIDTH-1, 10,1); GUI_DrawLine(WIDTH/2-1,11,WIDTH/2-1,HEIGHT-1,1); GUI_DrawLine(WIDTH/2-1,10+(HEIGHT-10)/2-1,WIDTH-1,10+(HEIGHT-10)/2-1,1); GUI_ShowString(0,1,"2021-08-1",8,18); GUI_ShowString(14,HEIGHT-1-10,"Cloudy",8,1); GUI_ShowString(WIDTH/2-1+2,13,"TEMP",8,1); GUI_DrawCircle(WIDTH-1-19, 25, 1,2); GUI_ShowString(WIDTH-1-14,20,"C",16,1); GUI_ShowString(WIDTH/2-1+2,39,"HUMI",8,1); GUI_DrawBMP(6,16,51,32, BMP5, 1); //温湿度的显示 GUI_ShowNum(WIDTH/2-1+9,20,b,1,16,1); //温度的十位数字显示 GUI_ShowNum(WIDTH/2-1+9+8,20,c,1,16,1); //温度的十位数字显示 GUI_ShowString(WIDTH/2-1+9+8+8,20,".",16,1); //小数点显示 GUI_ShowNum(WIDTH/2-1+9+8+16,20,d,1,16,1); //温度的小数显示 GUI_ShowNum(WIDTH/2-1+5,46,f,1,16,1); //湿度的十位显示 GUI_ShowNum(WIDTH/2-1+5+8,46,g,1,16,1); //湿度的个位显示 GUI_ShowString(WIDTH/2-1+5+8+8,46,"/rh",16,1); //湿度的单位显示 sleep(2); }步骤1 在motor_demo()中去调用hi_void motor_demo(hi_void) { int ret; motor_gpio_io_init(); //智能风扇模块gpio的初始化 SHT3X_init(); //温湿度传感器的初始化 motor_pwm_init(); //pwm的初始化 hi_spi_deinit(HI_SPI_ID_0); screen_spi_master_init(0); //spi的初始化 ret = hi_task_create(&g_MonitorTask, // task标识 // &MonitorTaskAttr, MonitorOledTask, // oled task处理函数 // NULL); // task处理函数参数 // if (ret < 0) { printf("Create monitor oled task failed [%d]\r\n", ret); return; } ret = hi_task_create(&g_MonitorTask, // task标识 // &MonitorTaskAttr, MonitorMotorTask, // motor task处理函数 // NULL); // task处理函数参数 // if (ret < 0) { printf("Create monitor motor task failed [%d]\r\n", ret); return; } ret = hi_task_create(&g_MonitorTask, // task标识 // &MonitorTaskAttr, MonitorShtTask, // sht task处理函数 // NULL); // task处理函数参数 // if (ret < 0) { printf("Create monitor motor task failed [%d]\r\n", ret); return; } return; }修改 applications / sample / wifi-iot / app / 路径下 BUILD.gn 文件,指定 motor_module 参与编译。"22_KP_SHT30_example:motor_module",运行结果将智能风扇模块和oled模块安装在开发板上,将上面编译好的程序下载到模组上验证温湿度的显示。原文地址:【OpenHarmony样例】基于启航KP_IOT开发板的智能风扇模块 - OpenHarmony开源社区 - 电子技术论坛 - 广受欢迎的专业电子论坛! (elecfans.com)
-
背景随着移动互联网的发展,WiFi已成为人们生活中不可或缺的网络接入方式。但在连接WiFi时,用户常需要手动输入一个复杂的密钥,这带来了一定的不便。针对这一痛点,利用QR码连接WiFi的方案应运而生。QR码连接WiFi的工作流程是:商家或公共场所提供含有WiFi密钥的QR码,用户只需使用手机扫一扫即可读取密钥信息并连接WiFi,无需手动输入,这种连接方式大大简化了用户的操作。随着智能手机摄像头识别能力的提升,以及用户需求的引领,利用QR码连接WiFi的方式未来还将得到更广泛的应用,为用户提供更稳定便捷的上网体验。它利用了移动互联网时代的技术优势,解决了传统WiFi连接中的痛点,是一种值得推广的网络连接效果方式。效果页面截图扫码页面 配网连接中 配网连接成功 配网连接失败 优势使用QR码连接WiFi具有以下优势:1.提高了连接成功率,避免因手动输入密钥错误导致的连接失败问题。2.加快了连接速度,扫码相对于手动输入更高效方便。3.提升了用户体验,无需记忆和输入复杂密钥,操作更人性化。4.方便密钥分享和更改,通过更新QR码即可实现。5.在一些需要频繁连接不同WiFi的场景下尤其便利,如酒店、餐厅、机场等。6.一些App可以自动识别WiFi二维码,实现零点击连接。开发与实现开发环境开发平台:windows10、DevEco Studio 3.1 Release 系统:OpenHarmony 3.2 Release,API9(Full SDK 3.2.11.9) 设备:SD100(工业平板设备、平台:RK3568、屏幕像素:1920 * 1200)项目开发需求分析1、支持相机扫码,并可以解析二维码信息;2、获取二维码中的wifi连接信息,自动完成网络连接;3、网络连接成功,则提示用户成功;4、网络连接失败,则提示用户失败,可以重新连接;5、UI界面符合OpenHarmony设计原则,应用界面简洁高效、自然流畅。项目流程图界面说明:从需求上分析,可以有两个界面,一是扫码界面、二是wifi连接等待和显示结果界面。 详细开发一、创建项目说明:通过DevEco Studio创建一个OpenHarmony的项目。二、申请权限说明:在应用中涉及到使用相机和wifi的操作,需要动态申请一些必要的权限,我们可以在 EntryAbility.ts中实现,EntryAbility.ts继承UIAbility,用于管理应用的生面周期,在OnCreate是实例冷启动时触发,在此函数中实现权限申请。具体代码如下:let permissionList: Array<Permissions> = [ "ohos.permission.GET_WIFI_INFO", "ohos.permission.INTERNET", 'ohos.permission.CAMERA', 'ohos.permission.READ_MEDIA', 'ohos.permission.WRITE_MEDIA', 'ohos.permission.MEDIA_LOCATION', 'ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION' ] onCreate(want, launchParam) { hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); this.requestPermissions() } private requestPermissions() { let AtManager = abilityAccessCtrl.createAtManager() AtManager.requestPermissionsFromUser(this.context, permissionList).then(async (data) => { Logger.info(`${TAG} data permissions: ${JSON.stringify(data.permissions)}`) Logger.info(`${TAG} data authResult: ${JSON.stringify(data.authResults)}`) // 判断授权是否完成 let resultCount: number = 0 for (let result of data.authResults) { if (result === 0) { resultCount += 1 } } let permissionResult : boolean = false if (resultCount === permissionList.length) { permissionResult = true } AppStorage.SetOrCreate(KEY_IS_PERMISSION, true) this.sendPermissionResult(permissionResult) }) } sendPermissionResult(result : boolean) { let eventData: emitter.EventData = { data: { "result": result } }; let innerEvent: emitter.InnerEvent = { eventId: EVENT_PERMISSION_ID, priority: emitter.EventPriority.HIGH }; emitter.emit(innerEvent, eventData); Logger.info(`${TAG} sendPermissionResult`) } onDestroy() { Logger.info(`${TAG} onDestroy`) emitter.off(EVENT_PERMISSION_ID) } let permissionList: Array<Permissions> = [ "ohos.permission.GET_WIFI_INFO", "ohos.permission.INTERNET", 'ohos.permission.CAMERA', 'ohos.permission.READ_MEDIA', 'ohos.permission.WRITE_MEDIA', 'ohos.permission.MEDIA_LOCATION', 'ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION' ] onCreate(want, launchParam) { hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); this.requestPermissions() } private requestPermissions() { let AtManager = abilityAccessCtrl.createAtManager() AtManager.requestPermissionsFromUser(this.context, permissionList).then(async (data) => { Logger.info(`${TAG} data permissions: ${JSON.stringify(data.permissions)}`) Logger.info(`${TAG} data authResult: ${JSON.stringify(data.authResults)}`) // 判断授权是否完成 let resultCount: number = 0 for (let result of data.authResults) { if (result === 0) { resultCount += 1 } } let permissionResult : boolean = false if (resultCount === permissionList.length) { permissionResult = true } AppStorage.SetOrCreate(KEY_IS_PERMISSION, true) this.sendPermissionResult(permissionResult) }) } sendPermissionResult(result : boolean) { let eventData: emitter.EventData = { data: { "result": result } }; let innerEvent: emitter.InnerEvent = { eventId: EVENT_PERMISSION_ID, priority: emitter.EventPriority.HIGH }; emitter.emit(innerEvent, eventData); Logger.info(`${TAG} sendPermissionResult`) } onDestroy() { Logger.info(`${TAG} onDestroy`) emitter.off(EVENT_PERMISSION_ID) }代码解析1、在应用中使用到相机和操作wifi需要根据需要动态申请相关权限,具体的权限用途可以查看:应用权限列表2、应用动态授权需要使用到@ohos.abilityAccessCtrl (程序访问控制管理),通过abilityAccessCtrl.createAtManager()获取到访问控制对象 AtManager。3、通过AtManager.requestPermissionsFromUser() 拉起请求用户授权弹窗,由用户动态授权。4、授权成功后通过Emitter(@ohos.events.emitter)向主界面发送授权结果。5、在onDestroy()应用退出函数中取消Emitter事件订阅。三、首页说明:首页即为扫码页面,用于识别二维码获取二维码信息,为网络连接准备。所以此页面有有个功能,加载相机和识别二维码。媒体相机相机的启动借鉴社区提供的代码案例:二维码扫码相机功能在CameraServices中,源码参考CameraServices.ets获取相机实例使用到媒体相机接口@ohos.multimedia.camera (相机管理)。首先使用camera.getCameraManager方法获取相机管理器,然后使用cameraManager.getSupportedCameras方法得到设备列表, 这里默认点亮列表中的首个相机;打开相机:使用 cameraManager.createCameraInput方法创建CameraInput实例,调用open方法打开相机;获取相机输出流:使用getSupportedOutputCapability查询相机设备在模式下支持的输出能力,然后使用createPreviewOutput创建相机输出流。获取拍照输出流,使用@ohos.multimedia.image接口的 createImageReceiver 方法创建ImageReceiver实例,并通过其getReceivingS_urfaceId()获取S_urfaceId,通过CameraManager.createPhotoOutput()函数构建拍照输出流,并将imageReceive 的 S_urfaceId与其建立绑定关系。 获取相片输出:首先使用createCaptureSession方法创建捕获会话的实例,然后使用beginConfig方法配置会话,接下来使用addInput方法添加一个摄像头输入流,使用addOutput添加一个摄像头和相机照片的输出流,使用commitConfig方法提交会话配置后,调用会话的start方法开始捕获相片输出。这里也可以使用相机预览流获取图像数据,但在界面上需要预览,所以这里需要构建两条预览流,一条预览流用于显示,在XComponent组件中渲染,另外一条预览流用于获取头像数据用于解析,根据实践发现,开启两条预览流后,相机帧率为:7fsp,表现为预览卡顿,所以为提升预览效果,使用定时拍照的方式获取图像数据。获取图像的在SaveCameraAsset.ets中实现,扫码页面启动后每间隔1.5s调用PhotoOutput.capture()实现拍照,通过imageReceiver.on(‘imageArrival’)接收图片,使用imageReceiver.readNextImage()获取图像对象,通过Image.getComponent()获取图像缓存数据。具体实现代码:CameraServiceimport camera from '@ohos.multimedia.camera'; import image from '@ohos.multimedia.image'; import SaveCameraAsset from './SaveCameraAsset' import { QRCodeScanConst, SCAN_TYPE } from './QRCodeScanConst' import { Logger } from '@ohos/common' import common from '@ohos.app.ability.common' let TAG: string = 'CameraService' /** * 拍照保存图片回调 */ export interface FunctionCallBack { onCaptureSuccess(thumbnail: image.PixelMap, resourceUri: string): void onCaptureFailure(): void onRecordSuccess(thumbnail: image.PixelMap): void onRecordFailure(): void /** * 缩略图 */ thumbnail(thumbnail: image.PixelMap): void /** * AI 识别结果 * @param result 识别结果 */ aiResult(result: string): void } export interface PreviewCallBack { onFrameStart() onFrameEnd() } export interface MetaDataCallBack { onRect(rect: camera.Rect) } export default class CameraService { private static instance: CameraService = null private mCameraManager: camera.CameraManager = null private mCameraCount: number = 0 // 相机总数 private mCameraMap: Map<string, Array<camera.CameraDevice>> = new Map() private mCurCameraDevice: camera.CameraDevice = null private mCameraInput: camera.CameraInput = null private mPreviewOutput: camera.PreviewOutput = null private mPreviewOutputByImage: camera.PreviewOutput = null private mPhotoOutput: camera.PhotoOutput = null private mSaveCameraAsset: SaveCameraAsset = new SaveCameraAsset() private mCaptureSession: camera.CaptureSession private mMetadataOutput: camera.MetadataOutput private constructor() { } /** * 单例 */ public static getInstance(): CameraService { if (this.instance === null) { this.instance = new CameraService() } return this.instance } /** * 初始化 */ public async initCamera(): Promise<number> { Logger.info(`${TAG} initCamera`) if (this.mCameraManager === null) { this.mCameraManager = camera.getCameraManager(AppStorage.Get('context')) // 注册监听相机状态变化 this.mCameraManager.on('cameraStatus', (cameraStatusInfo) => { Logger.info(`${TAG} camera Status: ${JSON.stringify(cameraStatusInfo)}`) }) // 获取相机列表 let cameras: Array<camera.CameraDevice> = this.mCameraManager.getSupportedCameras() if (cameras) { this.mCameraCount = cameras.length Logger.info(`${TAG} mCameraCount: ${this.mCameraCount}`) if (this.mCameraCount === 0) { return this.mCameraCount } for (let i = 0; i < cameras.length; i++) { Logger.info(`${TAG} --------------Camera Info-------------`) const tempCameraId: string = cameras[i].cameraId Logger.info(`${TAG} camera_id: ${tempCameraId}`) Logger.info(`${TAG} cameraPosition: ${cameras[i].cameraPosition}`) Logger.info(`${TAG} cameraType: ${cameras[i].cameraType}`) const connectionType = cameras[i].connectionType Logger.info(`${TAG} connectionType: ${connectionType}`) // 判断本地相机还是远程相机 if (connectionType === camera.ConnectionType.CAMERA_CONNECTION_BUILT_IN) { // 本地相机 this.displayCameraDevice(QRCodeScanConst.LOCAL_DEVICE_ID, cameras[i]) } else if (connectionType === camera.ConnectionType.CAMERA_CONNECTION_REMOTE) { // 远程相机 相机ID格式 : deviceID__Camera_cameraID 例如:3c8e510a1d0807ea51c2e893029a30816ed940bf848754749f427724e846fab7__Camera_lcam001 const cameraKey: string = tempCameraId.split('__Camera_')[0] Logger.info(`${TAG} cameraKey: ${cameraKey}`) this.displayCameraDevice(cameraKey, cameras[i]) } } // todo test 选择首个相机 this.mCurCameraDevice = cameras[0] Logger.info(`${TAG} mCurCameraDevice: ${this.mCurCameraDevice.cameraId}`) } } return this.mCameraCount } /** * 处理相机设备 * @param key * @param cameraDevice */ private displayCameraDevice(key: string, cameraDevice: camera.CameraDevice) { Logger.info(`${TAG} displayCameraDevice ${key}`) if (this.mCameraMap.has(key) && this.mCameraMap.get(key)?.length > 0) { Logger.info(`${TAG} displayCameraDevice has mCameraMap`) // 判断相机列表中是否已经存在此相机 let isExist: boolean = false for (let item of this.mCameraMap.get(key)) { if (item.cameraId === cameraDevice.cameraId) { isExist = true break } } // 添加列表中没有的相机 if (!isExist) { Logger.info(`${TAG} displayCameraDevice not exist , push ${cameraDevice.cameraId}`) this.mCameraMap.get(key).push(cameraDevice) } else { Logger.info(`${TAG} displayCameraDevice has existed`) } } else { let cameras: Array<camera.CameraDevice> = [] Logger.info(`${TAG} displayCameraDevice push ${cameraDevice.cameraId}`) cameras.push(cameraDevice) this.mCameraMap.set(key, cameras) } } /** * 创建相机输入流 * @param cameraIndex 相机下标 * @param deviceId 设备ID */ public async createCameraInput(cameraIndex?: number, deviceId?: string) { Logger.info(`${TAG} createCameraInput`) if (this.mCameraManager === null) { Logger.error(`${TAG} mCameraManager is null`) return } if (this.mCameraCount <= 0) { Logger.error(`${TAG} not camera device`) return } if (this.mCameraInput) { this.mCameraInput.close() } if (deviceId && this.mCameraMap.has(deviceId)) { if (cameraIndex < this.mCameraMap.get(deviceId)?.length) { this.mCurCameraDevice = this.mCameraMap.get(deviceId)[cameraIndex] } else { this.mCurCameraDevice = this.mCameraMap.get(deviceId)[0] } } Logger.info(`${TAG} mCurCameraDevice: ${this.mCurCameraDevice?.cameraId}`) try { this.mCameraInput = this.mCameraManager.createCameraInput(this.mCurCameraDevice) Logger.info(`${TAG} mCameraInput: ${JSON.stringify(this.mCameraInput)}`) this.mCameraInput.on('error', this.mCurCameraDevice, (error) => { Logger.error(`${TAG} CameraInput error: ${JSON.stringify(error)}`) }) await this.mCameraInput.open() } catch (err) { if (err) { Logger.error(`${TAG} failed to createCameraInput`) } } } /** * 释放相机输入流 */ public async releaseCameraInput() { Logger.info(`${TAG} releaseCameraInput`) if (this.mCameraInput) { try { await this.mCameraInput.close() Logger.info(`${TAG} releaseCameraInput closed`) } catch (err) { Logger.error(`${TAG} releaseCameraInput ${err}}`) } this.mCameraInput = null } } /** * 创建相机预览输出流 */ public async createPreviewOutput(s_urfaceId: string, callback?: PreviewCallBack) { Logger.info(`${TAG} createPreviewOutput s_urfaceId ${s_urfaceId}`) if (this.mCameraManager === null) { Logger.error(`${TAG} createPreviewOutput mCameraManager is null`) return } // 获取当前相机设备支持的输出能力 let cameraOutputCap = this.mCameraManager.getSupportedOutputCapability(this.mCurCameraDevice) if (!cameraOutputCap) { Logger.error(`${TAG} createPreviewOutput getSupportedOutputCapability error}`) return } Logger.info(`${TAG} createPreviewOutput cameraOutputCap ${JSON.stringify(cameraOutputCap)}`) let previewProfilesArray = cameraOutputCap.previewProfiles let previewProfiles: camera.Profile if (!previewProfilesArray || previewProfilesArray.length <= 0) { Logger.error(`${TAG} createPreviewOutput previewProfilesArray error}`) previewProfiles = { format: 1, size: { width: QRCodeScanConst.DEFAULT_WIDTH, height: QRCodeScanConst.DEFAULT_HEIGHT } } } else { Logger.info(`${TAG} createPreviewOutput previewProfile length ${previewProfilesArray.length}`) previewProfiles = previewProfilesArray[0] } Logger.info(`${TAG} createPreviewOutput previewProfile[0] ${JSON.stringify(previewProfiles)}`) try { this.mPreviewOutput = this.mCameraManager.createPreviewOutput(previewProfiles, s_urfaceId) Logger.info(`${TAG} createPreviewOutput success`) // 监听预览帧开始 this.mPreviewOutput.on('frameStart', () => { Logger.info(`${TAG} createPreviewOutput camera frame Start`) if (callback) { callback.onFrameStart() } }) this.mPreviewOutput.on('frameEnd', () => { Logger.info(`${TAG} createPreviewOutput camera frame End`) if (callback) { callback.onFrameEnd() } }) this.mPreviewOutput.on('error', (error) => { Logger.error(`${TAG} createPreviewOutput error: ${error}`) }) } catch (err) { Logger.error(`${TAG} failed to createPreviewOutput ${err}`) } } /** * 释放预览输出流 */ public async releasePreviewOutput() { Logger.info(`${TAG} releaseCamera PreviewOutput`) if (this.mPreviewOutput) { await this.mPreviewOutput.release() Logger.info(`${TAG} releaseCamera PreviewOutput release`) this.mPreviewOutput = null } } /** * 创建拍照输出流 */ public async createPhotoOutput(functionCallback: FunctionCallBack) { Logger.info(`${TAG} createPhotoOutput`) if (!this.mCameraManager) { Logger.error(`${TAG} createPhotoOutput mCameraManager is null`) return } // 通过宽、高、图片格式、容量创建ImageReceiver实例 const receiver: image.ImageReceiver = image.createImageReceiver(QRCodeScanConst.DEFAULT_WIDTH, QRCodeScanConst.DEFAULT_HEIGHT, image.ImageFormat.JPEG, 8) const imageS_urfaceId: string = await receiver.getReceivingS_urfaceId() Logger.info(`${TAG} createPhotoOutput imageS_urfaceId: ${imageS_urfaceId}`) let cameraOutputCap = this.mCameraManager.getSupportedOutputCapability(this.mCurCameraDevice) Logger.info(`${TAG} createPhotoOutput cameraOutputCap ${cameraOutputCap}`) if (!cameraOutputCap) { Logger.error(`${TAG} createPhotoOutput getSupportedOutputCapability error}`) return } let photoProfilesArray = cameraOutputCap.photoProfiles let photoProfiles: camera.Profile if (!photoProfilesArray || photoProfilesArray.length <= 0) { // 使用自定义的配置 photoProfiles = { format: camera.CameraFormat.CAMERA_FORMAT_JPEG, size: { width: QRCodeScanConst.DEFAULT_WIDTH, height: QRCodeScanConst.DEFAULT_HEIGHT } } } else { Logger.info(`${TAG} createPhotoOutput photoProfile length ${photoProfilesArray.length}`) photoProfiles = photoProfilesArray[0] } Logger.info(`${TAG} createPhotoOutput photoProfile ${JSON.stringify(photoProfiles)}`) try { this.mPhotoOutput = this.mCameraManager.createPhotoOutput(photoProfiles, imageS_urfaceId) Logger.info(`${TAG} createPhotoOutput mPhotoOutput success`) // 保存图片 this.mSaveCameraAsset.saveImage(receiver, functionCallback) } catch (err) { Logger.error(`${TAG} createPhotoOutput failed to createPhotoOutput ${err}`) } } /** * 释放拍照输出流 */ public async releasePhotoOutput() { Logger.info(`${TAG} releaseCamera PhotoOutput`) if (this.mPhotoOutput) { await this.mPhotoOutput.release() Logger.info(`${TAG} releaseCamera PhotoOutput release`) this.mPhotoOutput = null } } public async createSession() { Logger.info(`${TAG} createSession`) this.mCaptureSession = await this.mCameraManager.createCaptureSession() Logger.info(`${TAG} createSession mCaptureSession ${this.mCaptureSession}`) this.mCaptureSession.on('error', (error) => { Logger.error(`${TAG} CaptureSession error ${JSON.stringify(error)}`) }) try { this.mCaptureSession?.beginConfig() this.mCaptureSession?.addInput(this.mCameraInput) if (this.mPreviewOutputByImage != null) { Logger.info(`${TAG} createSession addOutput PreviewOutputByImage`) this.mCaptureSession?.addOutput(this.mPreviewOutputByImage) } if (this.mPreviewOutput != null) { Logger.info(`${TAG} createSession addOutput PreviewOutput`) this.mCaptureSession?.addOutput(this.mPreviewOutput) } if (this.mPhotoOutput != null) { Logger.info(`${TAG} createSession addOutput PhotoOutput`) this.mCaptureSession?.addOutput(this.mPhotoOutput) } if (this.mMetadataOutput != null) { Logger.info(`${TAG} createSession addOutput mMetadataOutput`) this.mCaptureSession?.addOutput(this.mMetadataOutput) } } catch (err) { if (err) { Logger.error(`${TAG} createSession beginConfig fail err:${JSON.stringify(err)}`) } } try { await this.mCaptureSession?.commitConfig() } catch (err) { if (err) { Logger.error(`${TAG} createSession commitConfig fail err:${JSON.stringify(err)}`) } } try { await this.mCaptureSession?.start() } catch (err) { if (err) { Logger.error(`${TAG} createSession start fail err:${JSON.stringify(err)}`) } } if (this.mMetadataOutput) { this.mMetadataOutput.start().then(() => { Logger.info(`${TAG} Callback returned with metadataOutput started`) }).catch((err) => { Logger.error(`${TAG} Failed to metadataOutput start ${err.code}`) }) } Logger.info(`${TAG} createSession mCaptureSession start`) } public async releaseSession() { Logger.info(`${TAG} releaseCamera Session`) if (this.mCaptureSession) { await this.mCaptureSession.release() Logger.info(`${TAG} releaseCamera Session release`) this.mCaptureSession = null } } /** * 拍照 */ public async takePicture() { Logger.info(`${TAG} takePicture`) if (!this.mCaptureSession) { Logger.info(`${TAG} takePicture session is release`) return } if (!this.mPhotoOutput) { Logger.info(`${TAG} takePicture mPhotoOutput is null`) return } try { const photoCaptureSetting: camera.PhotoCaptureSetting = { quality: camera.QualityLevel.QUALITY_LEVEL_HIGH, rotation: camera.ImageRotation.ROTATION_0, location: { latitude: 0, longitude: 0, altitude: 0 }, mirror: false } await this.mPhotoOutput.capture(photoCaptureSetting) } catch (err) { Logger.error(`${TAG} takePicture err:${JSON.stringify(err)}`) } } /** * 获取设备的相机列表 * @param deviceId 设备ID */ public getDeviceCameras(deviceId: string): Array<camera.CameraDevice> { Logger.info(`${TAG} getDeviceCameras ${deviceId} size ${this.mCameraMap.size}`) return this.mCameraMap.get(deviceId) } public getCameraCount(): number { return this.mCameraCount } /** * 释放相机 */ public async releaseCamera(): Promise<boolean> { Logger.info(`${TAG} releaseCamera`) let result: boolean = false let tempStartTime: number = new Date().getTime() try { await this.releaseCameraInput() await this.releasePhotoOutput() await this.releasePreviewOutput() await this.releaseSession() result = true } catch (err) { Logger.error(`${TAG} releaseCamera fail ${JSON.stringify(err)}`) } let tempTime: number = new Date().getTime() - tempStartTime Logger.info(`${TAG} releaseCamera finish time: ${tempTime}`) return result } public async selectPic() { Logger.info("getSingleImageFromAlbum start") let context = AppStorage.Get('context') as common.UIAbilityContext let abilityResult = await context.startAbilityForResult({ bundleName: 'com.ohos.photos', abilityName: 'com.ohos.photos.MainAbility', parameters: { uri: 'singleselect' // 只选取单个文件 } }) if (abilityResult.want === null || abilityResult.want === undefined) { Logger.info("getSingleImageFromAlbum end. abilityResult.want is null.") return null } if (abilityResult.want.parameters === null || abilityResult.want.parameters === undefined) { Logger.info("getSingleImageFromAlbum end. abilityResult.want.parameters is null.") return null } let images = abilityResult.want.parameters['select-item-list'] let imageUri = images[0] Logger.info("getSingleImageFromAlbum end. uri:" + imageUri) return imageUri } }SaveCameraAssetimport image from '@ohos.multimedia.image' import { FunctionCallBack } from './CameraService' import { Logger } from '@ohos/common' import CodeRuleUtil from '../utils/CodeRuleUtil' const TAG: string = 'SaveCameraAsset' /** * 保存相机拍照的资源 */ export default class SaveCameraAsset { constructor() { } /** * 保存拍照图片 * @param imageReceiver 图像接收对象 * @param thumbWidth 宽度 * @param thumbHeight 高度 * @param callback 回调 */ public saveImage(imageReceiver: image.ImageReceiver, callback: FunctionCallBack) { console.info(`${TAG} saveImage`) let buffer = new ArrayBuffer(4096) const imgWidth: number = imageReceiver.size.width const imgHeight: number = imageReceiver.size.height Logger.info(`${TAG} saveImage size ${JSON.stringify(imageReceiver.size)}`) // 接收图片回调 imageReceiver.on('imageArrival', async () => { console.info(`${TAG} saveImage ImageArrival`) // 使用当前时间命名 imageReceiver.readNextImage((err, imageObj: image.Image) => { if (imageObj === undefined) { Logger.error(`${TAG} saveImage failed to get valid image error = ${err}`) return } // 根据图像的组件类型从图像中获取组件缓存 4-JPEG类型 imageObj.getComponent(image.ComponentType.JPEG, async (errMsg, imgComponent) => { if (imgComponent === undefined) { Logger.error(`${TAG} getComponent failed to get valid buffer error = ${errMsg}`) return } if (imgComponent.byteBuffer) { Logger.info(`${TAG} getComponent imgComponent.byteBuffer ${imgComponent.byteBuffer.byteLength}`) buffer = imgComponent.byteBuffer // todo 内置解码库不开源 let resultRGB: string = qr.decode(buffer) Logger.info(`${TAG} AI uimg result RGB ${resultRGB}`) if (callback) { callback.aiResult(CodeRuleUtil.getRuleResult(resultRGB)) } } else { Logger.info(`${TAG} getComponent imgComponent.byteBuffer is undefined`) }解码说明:解码使用内部的解码库因为不开源,非常抱歉,当然可以使用开源解码可以,如jsqr、zxing"dependencies": { "jsqr": "^1.4.0", "@ohos/zxing": "^2.0.0" }四、配网协议说明:处于通用性考虑,需要对配网的二维码解析约定一个协议,也就是约定联网二维码数据的格式:##ssid##pwd##securityTypessid : 热点的SSID,编码格式为UTF-8。pwd :热点的密钥securityType : 加密类型,这可以参看wifiManager.WifiSecurityType在项目中也提供了协议解析类AnalyticResult.ts,具体代码如下:/** * 结果解析类 */ export type ResultType = { ssid: string, pwd: string, securityType : number } const SEPARATOR: string = '##' export class Analytic { constructor() { } getResult(msg: string): ResultType { let result: ResultType = null if (msg && msg.length > 0 && msg.indexOf(SEPARATOR) >= 0) { let resultArr: string[] = msg.split(SEPARATOR) if (resultArr.length >= 4) { result = { ssid: resultArr[1], pwd: resultArr[2], securityType: parseInt(resultArr[3]) } } } return result } }五、配网页面说明:通过对配网二维码的解析获取到热点的ssid、密钥、加密类型,就可以通过@ohos.wifiManager(WLAN)提供的网络连接接口实现配网。因为网络连接需要调用系统的一些验证流程,需要消耗一些时间,为了优化交互,需要一个网络连接等待界面ConnectPage.ets,界面截图如下:具体代码如下:import { WifiConnectStatus } from '../model/Constant' import router from '@ohos.router'; import { Logger } from '@ohos/common' import wifi from '@ohos.wifiManager'; import { ResultType } from '../model/AnalyticResult' import { WifiModel } from '../model/WifiModel' /** * 网络连接页面 */ const TAG: string = '[ConnectPage]' const MAX_TIME_OUT: number = 60000 // 最大超时时间 @Entry @Component struct ConnectPage { @State mConnectSsid: string = '' @State mConnectStatus: WifiConnectStatus = WifiConnectStatus.CONNECTING @State mConnectingAngle : number = 0 @State mConnectFailResource : Resource = $r('app.string.connect_wifi_fail') private linkedInfo: wifi.WifiLinkedInfo = null private mWifiModel: WifiModel = new WifiModel() private mTimeOutId: number = -1 private mAnimationTimeOutId : number = -1 async aboutToAppear() { Logger.info(`${TAG} aboutToAppear`) this.showConnecting() let wifiResult: ResultType = router.getParams()['wifiResult'] Logger.info(`${TAG} wifiResult : ${JSON.stringify(wifiResult)}`) // 如果wifi是开的,就记录下状态,然后扫描wifi,并获取连接信息 if (!wifi.isWifiActive()) { Logger.info(TAG, 'enableWifi') try { wifi.enableWifi() } catch (error) { Logger.error(`${TAG} wifi enable fail, ${JSON.stringify(error)}`) } } await this.getLinkedInfo() // 启动监听 this.addListener() if (wifiResult == null) { Logger.info(TAG, 'wifiResult is null') this.mConnectFailResource = $r('app.string.scan_code_data_error') this.mConnectStatus = WifiConnectStatus.FAIL } else { this.mConnectSsid = wifiResult.ssid Logger.info(`${TAG} connect wifi ${this.mConnectSsid}`) this.disposeWifiConnect(wifiResult) } } /** * 启动超时任务 */ startTimeOut(): void { Logger.info(TAG, `startTimeOut`) this.mTimeOutId = setTimeout(() => { // 如果超过1分钟没有连接上网络,则认为网络连接超时 try { this.mConnectFailResource = $r('app.string.connect_wifi_fail') this.mConnectStatus = WifiConnectStatus.FAIL wifi.disconnect(); } catch (error) { Logger.error(TAG, `failed,code:${JSON.stringify(error.code)},message:${JSON.stringify(error.message)}`) } }, MAX_TIME_OUT) } /** * 取消超时任务 */ cancelTimeOut() { Logger.info(TAG, `cancelTimeOut id:${this.mTimeOutId}`) if (this.mTimeOutId >= 0) { clearTimeout(this.mTimeOutId) this.mTimeOutId = -1 } } // 监听wifi的变化 addListener() { // 连接状态改变时,修改连接信息 wifi.on('wifiConnectionChange', async state => { Logger.info(TAG, `wifiConnectionChange: ${state}`) // 判断网络是否连接 0=断开 1=连接 if (state === 1) { this.mConnectStatus = WifiConnectStatus.SUCCESS this.cancelTimeOut() } await this.getLinkedInfo() }) // wifi状态改变时,先清空wifi列表,然后判断是否是开启状态,如果是就扫描 wifi.on('wifiStateChange', state => { Logger.info(TAG, `wifiStateLisener state: ${state}`) }) } // 获取有关Wi-Fi连接的信息,存入linkedInfo async getLinkedInfo() { try { let wifiLinkedInfo = await wifi.getLinkedInfo() if (wifiLinkedInfo === null || wifiLinkedInfo.bssid === '') { this.linkedInfo = null return } this.linkedInfo = wifiLinkedInfo } catch (err) { Logger.info(`getLinkedInfo failed err is ${JSON.stringify(err)}`) } } /** * 处理wifi连接 * @param wifiResult */ disposeWifiConnect(wifiResult: ResultType): void { this.mConnectStatus = WifiConnectStatus.CONNECTING if (this.linkedInfo) { // 说明wifi已经连接,需要确认需要连接的wifi和已连接的wifi是否为相同 let linkedSsid: string = this.linkedInfo.ssid; if (linkedSsid === wifiResult.ssid) { Logger.info(`${TAG} The same ssid`); this.mConnectStatus = WifiConnectStatus.SUCCESS return; } // 如果wifi不同,则先断开网络连接,再重新连接 try { wifi.disconnect(); this.connectWifi(wifiResult.ssid, wifiResult.pwd, wifiResult.securityType) } catch (error) { Logger.error(TAG, `failed,code:${JSON.stringify(error.code)},message:${JSON.stringify(error.message)}`) } } else { this.connectWifi(wifiResult.ssid, wifiResult.pwd, wifiResult.securityType) } } private connectWifi(ssid: string, pwd: string, securityType : number) { this.startTimeOut() this.mWifiModel.connectNetwork(ssid, pwd, securityType) } async gotoIndex() { try { let options: router.RouterOptions = { url: "pages/Index" } await router.replaceUrl(options) } catch (error) { Logger.error(`${TAG} go to index fail, err: ${JSON.stringify(error)}`) } } showConnecting() { this.mConnectingAngle = 0 this.mAnimationTimeOutId = setTimeout(() => { this.mConnectingAngle = 360 }, 500) } closeConnecting() { if (this.mAnimationTimeOutId > -1) { clearTimeout(this.mAnimationTimeOutId) } } aboutToDisappear() { wifi.off('wifiConnectionChange') wifi.off('wifiStateChange') this.cancelTimeOut() this.closeConnecting() } build() { Column() { // back Row() { Image($r('app.media.icon_back')) .width(30) .height(30) .objectFit(ImageFit.Contain) .onClick(() => { router.back() }) } .width('90%') .height('10%') .justifyContent(FlexAlign.Start) .alignItems(VerticalAlign.Center) Stack() { // 背景 Column() { Image($r('app.media.bg_connect_wifi')) .width('100%') .height('100%') .objectFit(ImageFit.Contain) .rotate({ x: 0, y: 0, z: 1, centerX: '50%', centerY: '49%', angle: this.mConnectingAngle }) .animation({ duration: 2000, // 动画时长 curve: Curve.Linear, // 动画曲线 delay: 0, // 动画延迟 iterations: -1, // 播放次数 playMode: PlayMode.Normal // 动画模式 }) } Column({ space: 20 }) { if (this.mConnectStatus === WifiConnectStatus.SUCCESS) { // 连接成功 Image($r('app.media.icon_connect_wifi_success')) .width(80) .height(80) .objectFit(ImageFit.Contain) Text($r('app.string.connect_wifi_success')) .fontSize(32) .fontColor($r('app.color.connect_wifi_text')) Text(this.mConnectSsid) .fontSize(22) .fontColor($r('app.color.connect_wifi_text')) } else if (this.mConnectStatus === WifiConnectStatus.FAIL) { // 连接失败 Image($r('app.media.icon_connect_wifi_fail')) .width(80) .height(80) .objectFit(ImageFit.Contain) Text(this.mConnectFailResource) .fontSize(32) .fontColor($r('app.color.connect_wifi_text')) Button($r('app.string.reconnect_wifi')) .width(260) .height(55) .backgroundColor($r('app.color.connect_fail_but_bg')) .onClick(() => { this.gotoIndex() }) } else { // 连接中 Image($r('app.media.icon_connect_wifi')) .width(100) .height(100) .objectFit(ImageFit.Contain) Text($r('app.string.connect_wifi_hint')) .fontSize(16) .fontColor($r('app.color.connect_wifi_text')) Text($r('app.string.connecting_wifi')) .fontSize(32) .fontColor($r('app.color.connect_wifi_text')) Text(this.mConnectSsid) .fontSize(22) .fontColor($r('app.color.connect_wifi_text')) } } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Center) } .width('100%') .height('80%') } .width('100%') .height('100%') .backgroundColor($r('app.color.connect_bg')) } }整个界面比较简单,主要显示当前的连接状态:连接中、连接成功、连接超时,特别强调连接超时,计划热点最长连接60s,如果在预定时间未连接成功,则显示超时,超时后可以通过重新配网按钮进行重新扫码连接,根据实际测试,在热点未打开状态下扫码连接耗时平均值12s。亮点界面中最大的亮点,增加了一个发光圆形的属性动画animation,圆形在2s内绕着z轴旋从0度转到360度。Image($r('app.media.bg_connect_wifi')) .width('100%') .height('100%') .objectFit(ImageFit.Contain) .rotate({ x: 0, y: 0, z: 1, centerX: '50%', centerY: '49%', angle: this.mConnectingAngle }) .animation({ duration: 2000, // 动画时长 curve: Curve.Linear, // 动画曲线 delay: 0, // 动画延迟 iterations: -1, // 播放次数 playMode: PlayMode.Normal // 动画模式 })六、网络自动连接说明:网络自动连接主要是通过@ohos.wifiManager(WLAN)提供的连接接口实现,具体代码如下:import wifi from '@ohos.wifiManager' import { Logger } from '@ohos/common' const TAG: string = '[WiFiModel]' export type WifiType = { ssid: string, bssid: string, securityType: wifi.WifiSecurityType, rssi: number, band: number, frequency: number, timestamp: number } export class WifiModel { async getScanInfos(): Promise<Array<WifiType>> { Logger.info(TAG, 'scanWifi begin') let wifiList: Array<WifiType> = [] let result: Array<wifi.WifiScanInfo> = [] try { result = await wifi.getScanResults() } catch (err) { Logger.info(TAG, `scan info err: ${JSON.stringify(err)}`) return wifiList } Logger.info(TAG, `scan info call back: ${result.length}`) for (var i = 0; i < result.length; ++i) { wifiList.push({ ssid: result[i].ssid, bssid: result[i].bssid, securityType: result[i].securityType, rssi: result[i].rssi, band: result[i].band, frequency: result[i].frequency, timestamp: result[i].timestamp }) } return wifiList } connectNetwork(wifiSsid: string, psw: string, securityType : number): void { Logger.debug(TAG, `connectNetwork bssid=${wifiSsid} securityType:${securityType}`) // securityType 加密类型默认:Pre-shared key (PSK)加密类型 let deviceConfig: wifi.WifiDeviceConfig = { ssid: wifiSsid, preSharedKey: psw, isHiddenSsid: false, securityType: securityType } try { wifi.connectToDevice(deviceConfig) Logger.info(TAG, `connectToDevice success`) } catch (err) { Logger.error(TAG, `connectToDevice fail err is ${JSON.stringify(err)}`) } try { wifi.addDeviceConfig(deviceConfig) } catch (err) { Logger.error(TAG, `addDeviceConfig fail err is ${JSON.stringify(err)}`) } } }网络连接主要是通过wifi.connectToDevice(deviceConfig)实现,其中:deviceConfig: wifi.WifiDeviceConfig为WLAN配置信息,在连接网络时必填三个参数ssid、preSharedKey、securityType。ssid:热点的SSIDpreSharedKey:热点密钥securityType:加密类型注意:在调用connectToDevice()函数连接网络时,如果网络已经连接,则需要先调用disconnect()接口断开网络后再执行。至此,你已经完成了扫码即可连接网络的应用。原文地址:cid:link_0
-
前提启航kp OpenHarmony环境搭建搭建好OpenHarmony环境未搭建好可以参考OpenHarmony docker环境搭建安装vscode下载好启航kp所需的开发包和样例下载地址搭建过程进入正确文件夹首先要进入 /home/openharmony 目录下,如果没有打开在vsc左上角找到文件,点击,然后找到打开文件夹,输入想要进入的目录。 能看到便代表成功进入。配置开发板所需要的文件在vendor文件夹内新增文件夹:isoftstone,把从gitee下载文件中vendor_isoftstone文件夹中的qihang文件夹复制到isoftstone目录下。 device/board文件夹内新增文件夹:isoftstone,在isoftstone文件夹中新建文件夹:qihang,再将附加包内的board_qihang文件夹的人内容拷贝到这个文件夹内: 检验是否安装成功输入hb set 选中启qihang输入hb build -f尝试编译 输出 qinghang build success则构建成功。制作“hello word”案例在device/board/isoftstone/qihang/app目录建一个目录:01hello,然后在这个目录下新建文件:hello.c内容如下 ```c #include "ohos_init.h"void hello_demo(void) { printf("hello word!\n") } SYS_RUN(oled_demo);2. 在hello.c同级别目录添加文件BUILD.gn,填入以下内容static_library("hello_demo"){ sources=["hello.c"] }3. 在app目录BUILD.gn添加01hello模块import("//build/lite/config/component/lite_component.gni")lite_component("app") { features = [ "01hello:hello_demo", ] }5. hb set选择启航开发板hb build -f进行编译有以下输出表示编译成功 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20230912/1694523331196418595.png) 6。 编译成功后可以在out/qihang/qihang/Hi3861_wifiiot_app_allinone.bin找到编译后的二进制文件
-
OpenHarmony docker环境搭建要求一台安装ubuntu的虚拟机,vscode软件安装docker在 Ubuntu 上安装 Docker 非常直接。我们将会启用 Docker 软件源,导入 GPG key,并且安装软件包。首先,更新软件包索引,并且安装必要的依赖软件,来添加一个新的 HTTPS 软件源:sudo apt update sudo apt install apt-transport-https ca-certificates curl gnupg-agent software-properties-common使用下面的 curl 导入源仓库的 GPG key:curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -将 Docker APT 软件源添加到你的系统:sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"现在,Docker 软件源被启用了,你可以安装软件源中任何可用的 Docker 版本。01.想要安装 Docker 最新版本,运行下面的命令。如果你想安装指定版本,跳过这个步骤,并且跳到下一步。sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io02.想要安装指定版本,首先列出 Docker 软件源中所有可用的版本:sudo apt update apt list -a docker-ce可用的 Docker 版本将会在第二列显示。docker-ce/focal 5:19.03.93-0ubuntu-focal amd64 通过在软件包名后面添加版本=<VERSION>来安装指定版本:sudo apt install docker-ce=<VERSION> docker-ce-cli=<VERSION> containerd.io 一旦安装完成,Docker 服务将会自动启动。你可以输入下面的命令,验证它: sudo systemctl status docker 输出将会类似下面这样:● docker.service - Docker Application Container Engine Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled) Active: active (running) since Thu 2020-05-21 14:47:34 UTC; 42s ago ...当一个新的 Docker 发布时,你可以使用标准的sudo apt update && sudo apt upgrade流程来升级 Docker 软件包。拉取镜像运行 Docker 软件,打开 CMD 命令行或者 PowerShell 终端,使用指令下载 docker 官方镜像:docker pull swr.cn-south-1.myhuaweicloud.com/openharmony-docker/openharmony-docker:1.0.0等待下载完成之后,使用 docker images 可以查看到已下载的 docker 镜像 此时镜像名称太长不方便使用,可以使用重命名操作对镜像重命名:docker image tag swr.cn-south-1.myhuaweicloud.com/openharmony-docker/openharmony-docker:1.0.0 openharmony-docker:1.0.0此时使用 docker images 再次查看镜像,发现多出一个名为 openharmony-docker:1.0.0 的镜像 可以执行docker rmi swr.cn-south-1.myhuaweicloud.com/openharmony-docker/openharmony-docker:1.0.0删除旧的镜像: 执行docker run -it openharmony-docker:1.0.0 指令可以运行镜像可以看到系统直接进入到了/home/openharmony,但是此时仅是容器运行成功了,还没有代码,无法完成开发,接下来需要获取代码OpenHarmony 代码获取通过git(不推荐,有时会卡死)首先要设置git用户名和邮箱,否则拉去代码时会报错git config --global user.name "Your Name" git config --global user.email "youremail@yourdomain.com"拉去代码repo init -u git@gitee.com:openharmony/manifest.git -b OpenHarmony-3.2-Release -g ohos:mini repo sync -c repo forall -c 'git lfs pull'通过httprepo init -u https://gitee.com/openharmony/manifest.git -b OpenHarmony-3.2-Release -g ohos:mini repo sync -c repo forall -c 'git lfs pull'检验输入hb -v如果出现版本号代表成功Vscode 安装开发板实验需要烧录固件,所以要用到 vscode 编辑器导出固件 到该网址下载最新版的 Visual Studio Code,简称 vscode,下载完成后执行安装. 网址: cid:link_0安装 vscode 插件打开安装好的 vscode 编辑器,点击左侧插件图标,输入 remote ssh 点击安装图标,等待安装完成,安装完成之后左侧会出现下面的图标 然后重复步骤安装入 dev contains 远程连接服务器3、添加服务器连接配置 点击该选项卡,会进入SSH TARGETS的添加,如下图所示:点击添加按钮,输入远程服务器的地址,账号和ip根据自己的情况进行修改,如下图所示:保存之后,会将刚才的连接信息存储在 C:\Users\chaxun.ssh\config 中。此时就可以在SSH TARGETS中看到添加的远程服务器地址了:4、连接服务器 右键选择远程服务器,如果远程服务器能够连接成功,此时会需要你输入密码:5、基本操作例如我点击Open Folder,这里就会让你选择打开的文件目录:点击ok按钮打开 /home/zhaxun 之后,会需要再次输入密码:启动和连接镜像启动镜像的方式可以是终端命令行,如果容器已启动,可以选择 attach 连接容器 点击连接容器之后,会弹出一个新窗口,点击左侧上面第一个图标,会显示该 容器内的文件系统(需要一点缓冲时间) 如果界面同以上截图不一样,而是如下所示: 就点击打开文件夹,在上方弹出的对话框输入/home/openharmony,再点击确 定, 等待片刻就会显示出文件列表,接下来就可以像操作本地文件一样打开文件 并进行编辑,然后修改一个文件:在顶部菜单栏点击终端,再选择新建终端,可以打开终端: 注意观察弹出来的终端,工作目录是否是/home/openharmony,如果不是请检 查前面的操作是否错误或有遗漏 在终端命令行输入:hb set 指令,再输入回车,到达选择开发板的界面,用鼠标 或键盘上下键选择 qemu_mini_system_demo,再敲回车:选择好了开发板,就可以执行构建命令: hb build -f 此时系统开始构建,由于我们使用的是 docker 环境,在制作镜像的时候环境已 经准备好了,所以不需要配置其他的脚本或者工具即可以达到编译固件的目的. 最后终端会显示编译成功,我们可以在 out 相应的目录下找到对应的固件,输 入:ls out/arm_mps2_an386/qemu_mini_system_demoqemu 模拟器的运行 由于模拟器的运行不涉及到具体的硬件,所以本实验不需要烧录步骤,在 docker 控制台终端输入./qemu-run,再输入 y 就能运行 qemu 模拟器了,下面是运 行状态的截图: 在 qemu 的终端里可以输入 help 命令查看该模拟器支持运行哪些指令: 同学们可以自行尝试列出来的指令退出模拟器的运行: 按住 Ctrl+a 然后同时放开,再按一下 x 键即可退出 qemu 终端可以看出,系统此处退出了 qemu 模拟器回到了 doc
-
引言在物联网(IoT)领域,开发板扮演着至关重要的角色,为开发人员提供了实验和原型设计的平台。而OpenHarmony作为一个开源、可信赖的操作系统,为开发人员提供了便利和灵活性。本篇博客将介绍基于OpenHarmony的启航开发板的基础操作,并引导读者进入物联网开发的精彩世界。前提准备需要一台Ubuntu虚拟机和vscode软件,详细搭建请看下面这篇博客cid:link_0基础操作1.hb set命令的使用在命令行终端输入hb set 命令可以看到可选择的设备。 进行上下键的选择,选中设备后回车即可。2.hb build -f 命令的使用hb build -f 命令是编译命令,在控制台输入命令后回车会进行编译,成功如下: 3.Hello World 案例3.1、在device/board/qihang/app目录下新建一个目录:01hello,然后在这个目录下新建文件:hello.c,填入以下内容:#include "ohos_init.h" /*hello world demo 入口函数*/ static void hello_demo(){ printf("hello world !\n"); } SYS_RUN(hello_demo);3.2、在hello.c同级别目录添加文件BUILD.gn,填入以下内容:#静态库 static_library("hello_demo1"){ sources=["hello.c"] }3.3、在app目录下的BUILD.gn文件内添加01hello模块,如下所示: 3.4、接下来就是用hb set 和hb build -f进行选择和编译就行了。
-
【摘要】OpenHarmony docker环境搭建需要一台安装Ubuntu的虚拟机,并且虚拟机中需要有VScode。整个搭建流程请参考这篇博客:OpenHarmony docker环境搭建-云社区-华为云 (huaweicloud.com)上篇博主是用Ubuntu的服务器进行环境搭建的,在使用VScode时用到SSH登录虚拟机。本篇采用Ubuntu桌面版对OpenHarmony docker环境进行搭建【不建议使用桌面版进行配置,博主这边遇到的问题都是桌面版的!!!】。1、安装Docker在虚拟机中安装Docker的步骤跟着官网的教程一步步来就行:Docker官方下载文档2、下载官方Docker镜像接下来的重点是下载官方docker镜像。运行Docker软件,使用以下命令下载docker官方镜像:docker pull swr.cn-south-1.myhuaweicloud.com/openharmony-docker/openharmony-docker:1.0.0下载完成之后,使用docker images 可以查看到已下载的docker镜像此时镜像名称太长不方便使用,可以使用重命名操作对镜像重命名:docker image tag swr.cn-south-1.myhuaweicloud.com/openharmony-docker/openharmony-docker:1.0.0 openharmony-docker:1.0.0此时使用 docker images 再次查看镜像,发现多出一个名为 openharmony-docker:1.0.0 的镜像可以执行 docker rmi swr.cn-south-1.myhuaweicloud.com/openharmony-docker/openharmony-docker:1.0.0 删除旧的镜像。执行 docker run -it openharmony-docker:1.0.0 指令可以运行镜像。3、OpenHarmony代码拉取这里拉取方法建议使用OpenHarmony docker环境搭建-云社区-华为云 (huaweicloud.com)这篇博客中的第二种:通过http获取代码,详细内容请参考该博客(这里不在详细讲解)。4、VScode下载4.1、VScode下载后打不开的问题博主这里在安装的过程中为了省事,将自己的用户权限设置成了root权限(为了方便在运行时不加sudo),这个操作也就导致我踩到了一个坑:VScode打不开的问题。这里之所以打不开,问题恰恰出现在root权限上,在Ubuntu默认的状态下,root权限是不允许打开VScode的,所以这里需要将原本对普通用户授予root权限的操作再做一次变成普通用户即可。可以参考这篇博主的文章做一遍:将普通用户授予root权限4.2、VScode 识别不了容器问题博主这里在桌面版的VScode中下载了Dev Containers插件,但是却在识别容器中发现,识别不到正在运行的容器!!!接着准备下载桌面版的docker,在官网中我查询到,要下载桌面版Docker需要进行KVM virtualization support(KVM虚拟化支持)。博主在这里遇到了第二个坑:虚拟机CPU不支持KVM虚拟化!!!博主这里的虚拟机不支持虚拟化!!!(VMware Worstation)所以就陷入了死循环,不过解决办法还是有的。既然桌面版的没法用,那就转战服务器版本!!!下载VMware Worstation Pro 17 版本,并且配置一台Ubuntu的服务器版,然后按照步骤从头来配置就行了。
-
8⽉21⽇晚,华为云HCSD校园沙龙·广西制造工程职业技术学院顺利举办!本次活动由华为云计算技术有限公司主办,广西制造工程职业技术学院承办。活动以物联网+鸿蒙为主题,围绕端云生态协同展开技术交流分享,现场华为云专家带领200余名学生了解物联网、鸿蒙技术知识及发展动态。华为自发布鸿蒙系统以来,就引起广泛大众的关注,经过几年的打磨,目前鸿蒙系统已初具规模,在8月4日第五届华为开发者大会2023上又正式发布了全新升级的HarmonyOS 4(以下简称鸿蒙4)。全面进化的智能终端操作系统,更是带来前所未有的个性化,高效、智慧、流畅、安全的革新和体验,如今鸿蒙系统已成为最具生命力的生态底座,为用户提供了全新体验和全场景智慧生活。在万物互联的时代,鸿蒙智联势必加速了物联网时代的真正到来,华为云IoT高级工程师王婵,则以“物联网+鸿蒙,打开校园新方式”为主题,向与会师生详细讲述了物联网与鸿蒙的关系演进、物联网+鸿蒙使能千行百业、当代大学生如何玩转物联网+鸿蒙等,还特别讲述了鸿蒙4在个性化、高效性、智慧化、流畅度以及安全性等五个方面进行了全新升级,带来了前所未有的个性化与高效交互体验,打破了传统操作系统呆板、扁平、无趣的特点,为学生们揭开物联网与鸿蒙系统的神秘面纱。在训练营环节,带领学生们体验了基于现实使用案例场景搭建的实验【10分钟快速体验恒温空调云端控制】,学习如何基于物联网平台,在应用侧或者设备接入控制台设置设备影子,将预置的温度通过设备影子下达属性修改给空调,以实现空调收到修改属性的要求后,自动调节温度。通过理论讲解+实验练习,学生们深入学习如何基于物联网平台实现端云互通、万物智联,并体验开发乐趣。活动现场,氛围十分活跃,学生们不仅踊跃参与实践,还和专家进行互动问答,切磋交流。本次华为云HCSD校园沙龙活动为同学们提供了一个与专家互动交流的平台,拓宽了他们的视野,激发了他们对于科技创新的热情和动力。华为云将继续致力于与高校合作,并为人才培养提供更多支持,共同推动数字产业人才生态发展。
上滑加载中
推荐直播
-
全面解析华为云EI-API服务:理论基础与实践应用指南
2024/11/29 周五 18:20-20:20
Alex 华为云学堂技术讲师
本期直播给大家带来的是理论与实践结合的华为云EI-API的服务介绍。从“主要功能,应用场景,实践案例,调用流程”四个维度来深入解析“语音交互API,文字识别API,自然语言处理API,图像识别API及图像搜索API”五大场景下API服务,同时结合实验,来加深开发者对API服务理解。
去报名 -
企业员工、应届毕业生、在读研究生共探项目实践
2024/12/02 周一 19:00-21:00
姚圣伟 在职软件工程师 昇腾社区优秀开发者 华为云云享专家 HCDG天津地区发起人
大神带你一键了解和掌握LeakyReLU自定义算子在ONNX网络中应用和优化技巧,在线分享如何入门,以及在工作中如何结合实际项目进行学习
即将直播 -
昇腾云服务ModelArts深度解析:理论基础与实践应用指南
2024/12/03 周二 14:30-16:30
Alex 华为云学堂技术讲师
如何快速创建和部署模型,管理全周期AI工作流呢?本期直播聚焦华为昇腾云服务ModelArts一站式AI开发平台功能介绍,同时结合基于ModelArts 的实践性实验,帮助开发者从理论到实验更好地理解和使用ModelArts。
去报名
热门标签