• [问题求助] PersistentStorage这个按着文档来折腾一天也不生效,请大佬帮忙看看
    我在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经常识别不到手机.
  • [demo资源] 基于润和DAYU200开发套件的OpenHarmony分布式音乐播放器 【转】
    润和大禹系列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出现以下错误应该如何解决?
    小熊派烧录过程中执行upload出现以下错误应该如何解决?
  • [技术干货] 【OpenHarmony样例】基于启航KP_IOT开发板的智能风扇模块【转】
    本示例将演示如何利用启航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)
  • [技术干货] OpenHarmony 扫码自动配网【转载】
    背景随着移动互联网的发展,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环境搭建
    前提启航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环境搭建
    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
  • [技术干货] 基于OpenHarmony的启航开发板的基础操作
    引言在物联网(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环境搭建所见的问题和解决
    【摘要】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的服务器版,然后按照步骤从头来配置就行了。
  • [HCSD校园沙龙] 广西首站!华为云HCSD校园沙龙走进广西制造工程职业技术学院!
    8⽉21⽇晚,华为云HCSD校园沙龙·广西制造工程职业技术学院顺利举办!本次活动由华为云计算技术有限公司主办,广西制造工程职业技术学院承办。活动以物联网+鸿蒙为主题,围绕端云生态协同展开技术交流分享,现场华为云专家带领200余名学生了解物联网、鸿蒙技术知识及发展动态。华为自发布鸿蒙系统以来,就引起广泛大众的关注,经过几年的打磨,目前鸿蒙系统已初具规模,在8月4日第五届华为开发者大会2023上又正式发布了全新升级的HarmonyOS 4(以下简称鸿蒙4)。全面进化的智能终端操作系统,更是带来前所未有的个性化,高效、智慧、流畅、安全的革新和体验,如今鸿蒙系统已成为最具生命力的生态底座,为用户提供了全新体验和全场景智慧生活。在万物互联的时代,鸿蒙智联势必加速了物联网时代的真正到来,华为云IoT高级工程师王婵,则以“物联网+鸿蒙,打开校园新方式”为主题,向与会师生详细讲述了物联网与鸿蒙的关系演进、物联网+鸿蒙使能千行百业、当代大学生如何玩转物联网+鸿蒙等,还特别讲述了鸿蒙4在个性化、高效性、智慧化、流畅度以及安全性等五个方面进行了全新升级,带来了前所未有的个性化与高效交互体验,打破了传统操作系统呆板、扁平、无趣的特点,为学生们揭开物联网与鸿蒙系统的神秘面纱。在训练营环节,带领学生们体验了基于现实使用案例场景搭建的实验【10分钟快速体验恒温空调云端控制】,学习如何基于物联网平台,在应用侧或者设备接入控制台设置设备影子,将预置的温度通过设备影子下达属性修改给空调,以实现空调收到修改属性的要求后,自动调节温度。通过理论讲解+实验练习,学生们深入学习如何基于物联网平台实现端云互通、万物智联,并体验开发乐趣。活动现场,氛围十分活跃,学生们不仅踊跃参与实践,还和专家进行互动问答,切磋交流。本次华为云HCSD校园沙龙活动为同学们提供了一个与专家互动交流的平台,拓宽了他们的视野,激发了他们对于科技创新的热情和动力。华为云将继续致力于与高校合作,并为人才培养提供更多支持,共同推动数字产业人才生态发展。
  • [HCSD校园沙龙] HCSD夏令营 · 厦门大学走进华为(龙岗)工业互联网创新中心
    8月10日上午,HCSD夏令营·厦门大学走进华为专场活动圆满落幕!厦门大学刘长青书记及师生等一行人至华为(龙岗)工业互联网创新中心参观交流。华为云工业软件人才培养合作经理欧阳航先生全方位介绍了工业互联网整个生命周期及展厅的布局,并带领厦大师生沉浸式体验了工业互联网创新中心的能力。工业软件是现代工业的灵魂,是制造大国迈向制造强国的基础,是研发创新摸高的底线能力。华为云工业软件产教融合总监侯建国基于工业软件人才培养的背景,展开“集众智、聚众力,为突破自主创新工业软件贡献力量”的主旨演讲,他提到,随着工业软件在工业产品的全生命周期性的广泛应用,核心工业软件能力更需要可信可用。华为以云计算框架为核心,开放华为工程场景,与伙伴共建自主创新工业软件体系;以工业数字模型驱动引擎,筑牢工业软件根技术底座。华为云开发者创新中心运营经理邓大启以“智能世界的云底座,支撑华为各大开发者生态发展”为背景,华为云协同鸿蒙生态,全面解读鸿蒙技术优势、鸿蒙ToB、ToC产品能力,带领开发者基于端云协同能力无限创新,从而构建优良生态,全方面使能开发者。人才因产业而聚,产业因人才而兴。发现、培育、凝聚一批有自主创新潜力、政治素质过硬、专业能力突出的卓越工程师,有助于形成“产业聚集人才、人才支撑产业”的良性互动。华为云招聘经理蔡云丽则围绕以华为云为底座的数字技术背景,讲解华为云的发展历程、业务进展、架构技术和应用。她还深度剖析行业发展方向,介绍华为云校招的岗位&流程,为学生们未来择业做行业洞察。华为(龙岗)工业互联网创新中心作为深圳首个工业互联网创新中心,依托于华为云的数字化赋能服务,致力于带动龙岗区整体制造业产业集群转型升级、提升工业互联网能力,助力龙岗区打造成为创新驱动、应用引领、生态活跃的全国工业互联网领先地区。截至目前,创新中心提供场景解决方案100余个,已服务区内工业企业1200余家。接下来,也将持续构建工业互联网产业生态,加速数字龙岗发展,推动龙岗区工业企业数字化转型、助力传统制造业向制造服务业升级。
  • [问题求助] ESP32可以用LiteOS吗?LiteOS有HarmonyOS的DSoftBus component吗?
    如题,我就是想在ESP32上用DSoftBus。
  • IoT+鸿蒙模拟灯光秀实验手册
    实验介绍开发者按指定的灯光效果要求编写联动规则,云端下发控制命令,端侧规则引擎解析执行;通过鸿蒙分布式软总线能力,实现主从设备联动控制,模拟实现预期灯光效果。前提条件1)已注册华为帐号。未注册可点击注册页面完成注册。2)已完成实名制认证。未完成可在华为云上点击实名认证完成认证,否则无法使用设备接入功能。开通设备接入服务1. 访问设备接入服务产品首页,单击“免费试用”。2. 根据界面提示,输入实例名称,并单击“立即创建”,即可开通标准版实例免费单元。注:如果是存量用户,请确认是“华北-北京四”区域,然后选择左侧导航栏“IoTDA实例”,单击右侧标准版卡片里的“开通免费单元”,完成开通。开通成功后,切换实例为标准版。开发灯光秀产品1、创建产品1.1 进入设备接入控制台,选择左侧导航栏“产品”,单击页面右上角“创建产品”,填写参数并“确定”。① 所属资源空间:默认资源空间② 产品名称:红绿灯产品③ 协议类型:MQTT④ 数据格式:JSON⑤ 设备类型选择:自定义类型⑥ 设备类型:红绿灯产品1.2 提示创建产品成功,单击“查看详情”。1.3 在“模型定义”页签,单击“上传模型文件”,选择添加桌面的【红绿灯产品模型文件.zip】,然后单击“确定”完成上传。添加完成,预览服务列表如下图所示:2、注册设备2.1 选择左侧导航栏“设备>所有设备”,单击页面右上角“注册设备”,填写参数并“确定”,完成设备A注册。① 所属资源空间:默认资源空间② 所属产品:选择上一步【创建产品】中已创建的产品③ 设备标识码:请按照开发板上的标签纸记录填写④ 设备名称:自定义,如红绿灯A(主)⑤ 设备ID:平台会自动生成设备id,请按照开发板上的标签纸记录填写⑥ 设备认证类型:选择“密钥”⑦ 密钥/确认密钥:固定为123456782.2 重复上述步骤,完成设备B注册。① 所属资源空间:默认资源空间② 所属产品:选择上一步【创建产品】中已创建的产品③ 设备标识码:请按照开发板上的标签纸记录填写④ 设备名称:自定义,如红绿灯B(从)⑤ 设备ID:平台会自动生成设备id,请按照开发板上的标签纸记录填写⑥ 设备认证类型:选择“密钥”⑦ 密钥/确认密钥:固定为12345678查看设备列表,设备为“未激活”状态,预览如下:3、创建鸿蒙软总线3.1 选择左侧导航栏“设备>群组”,单击“添加根群组”,填写参数并“确定”,完成群组添加。① 群组类型:静态群组② 群组名称:自定义,如红绿灯主从设备组3.2 选择刚添加的群组名称,单击绑定设备下的“绑定”,选择添加的2个设备,单击“确定”。并在弹窗中单击“确认”,完成设备绑定。3.3 选择刚添加的群组名称,单击鸿蒙软总线下的“点击创建”,填写鸿蒙软总线名称,然后单击“确定”。① 鸿蒙软总线名称:自定义,如红绿灯软总线提示鸿蒙软总线创建成功,单击“确定”。4、设备接入验证4.1 请检查分配的两套开发板设备,确认电源线、数据线连接正常;按Power键点亮屏幕,并将开发板设备连上自己的手机Wi-Fi热点。4.2 打开本地PC的命令提示符窗口,输入并执行如下命令查看设备信息,复制设备信息到文本中备用。hdc list targets注:需要安装开发板驱动及toolchains,并将toolchains路径配置到系统环境变量Path中。主从设备组示例如下:主设备序列号:7001005458323933328a017cdf9f3800从设备序列号:7001005458323933328a52dc386c3800Tips:在命令提示符窗口中,可以划词选择内容,Ctrl+c复制,鼠标右键粘贴。4.3 执行如下命令,连接开发板设备。其中,请将xxx替换为上面复制的设备序列号。hdc -t xxx shellTips:请打开两个命令提示符窗口,分别连接两个开发板设备。4.4 连接开发板设备成功后,请执行如下命令运行MQTT Demo程序。cd /bin;mount -o rw,remount /;chmod 777 MQTT_Demo;./MQTT_Demo注:请先在主设备运行Demo程序,后在从设备运行Demo程序,并保持窗口运行切勿关闭。Tips:Demo运行会持续打印日志,主设备为快速、从设备为中速;当主从设备软总线建立成功,打印日志均变为快速。4.5 进入设备接入控制台,选择左侧导航栏“设备>所有设备”,查看设备显示为“在线”状态,且红绿灯呈闪烁状态(待机模式1)。5、配置设备联动规则5.1 选择左侧导航栏“规则>设备联动”,单击“创建规则”。5.2 填写基本信息、触发条件和执行动作后,单击“创建规则”。① 所属资源空间:默认资源空间② 规则名称:自定义,如红绿灯联动规则1③ 规则类型:端侧执行④ 规则执行设备:红绿灯A(主)⑤ 生效时间:一直生效⑥ 触发条件:设备属性触发 | 红绿灯A(主) | MODE | model=1⑦ 执行条件:下发命令 | 红绿灯B(从) | MODE | mode | setMode=1创建成功,返回规则列表。6、下发命令选择左侧导航栏“设备>所有设备”,单击设备标识码或详情,进入红绿灯A(主)设备详情页;在“云端下发>命令下发”页签下,单击“命令下发”,设置命令参数并单击“确定”。① 选择命令:MODE: mode② setMode:17、预期结果红绿灯A(主):0~30s显示红灯,30~57s显示绿灯,57~60s显示黄灯;红绿灯B(从):0~27s显示绿灯,27~30s显示黄灯,30~60s显示红灯。附加任务开发板红绿灯工作模式说明:(待机模式为“模式0”)工作模式设备A(主)设备B(从)模式00-1 全亮,1-2 全灭0-1 全亮,1-2 全灭模式10-30s 红,30-57s 绿,57-60s 黄闪0-27s 绿 27-30s 黄闪 30-60s 红模式20-5s 红,5-10s 黄,10-15s 绿,15-30s 全灭0-15s 全灭,15-20s 红,20-25s 黄,25-30s 绿模式30-1s 红,1-2s 红黄,2-3s 红黄绿,3-6s 全亮0-3s 全灭,3-4s 红,4-5s 红黄,5-6s 红黄绿任务1通过设备联动组合模式,实现红绿灯A(主)红/黄/绿、红绿灯设备B(从)红/黄/绿,跑马灯效果。任务2通过设备联动组合模式,实现红绿灯设备A(主)、红绿灯设备B(从)在某一时刻,6灯全亮。
  • [活动公告] 华为云移动应用开发暑期师资培训来啦!!!
    为帮助高校教师更加全面系统了解华为云生态与鸿蒙技术,助力产教融合,更好的开展相关教学工作,探索更多人才培养新方案,华为云将于7月-8月开展暑期移动应用开发师资培训,诚邀各院校老师参与研修,具体培训计划如下:培训时间:2023年7月 -8月 形式:线下培训,每期5天,每天8课时,共计40 课时培训对象对华为云生态和鸿蒙感兴趣或计划了解相关内容的院校教师;从事计算机网络技术、移动应用技术与服务、软件与信息服务、软件技术、物联网工程等相关专业的教师或研究带头人;有计划了解和建设华为云开发者创新中心实训基地的院校教师。课程内容本次课程主要包含如何使用ArkTS开发HarmonyOS应用、HMS服务集成、OpenHarmony硬件开发相关技术知识,通过理论授课结合实操演练的方式,帮助院校教师扎实掌握课程知识,支撑院校更好的开展教学工作。线下培训标题活动时间承办院校华为云移动应用开发暑期师资培训·新疆7.3-7.7克拉玛依职业技术学院华为云移动应用开发暑期师资培训·安徽7.10-7.14安徽工业经济职业技术学院华为云移动应用开发暑期师资培训·福建7.17-7.21福州职业技术学院华为云移动应用开发暑期师资培训·河北7.24-7.28河北师范大学华为云移动应用开发暑期师资培训·深圳7.31-8.4深圳信息职业技术学院华为云移动应用开发暑期师资培训·广西8.21-8.  25广西制造工程职业技术学院相关说明请自带笔记本电脑参加本次培训,培训不提供OpenHarmony硬件开发设备,参培老师按自己需求选择是否自备(设备名称:润和HiSpark系列Pegasus智能家居开发套件);华为云侧不收取任何费用,培训费用由承办方经由成本核算后收取;完成培训并考核合格教师,将现场颁发华为云师资培训证书,同时可登录华为云官网在个人中心查看电子版证书;如因不可抗力原因造成培训不能按计划进行,主办单位将另行通知。联系方式培训相关咨询联系人:李雪:电话:15668332687(微信同号),邮箱:lixue90@huawei.com报名二维码   扫码后选择想要参加的时间与城市并填写报名问卷报名