diff --git "a/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/README.md" "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/README.md" new file mode 100644 index 0000000000000000000000000000000000000000..783f7c52e3215f3a81fac7dcc646d146b73b604c --- /dev/null +++ "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/README.md" @@ -0,0 +1,487 @@ +# 任务介绍 + +在Hi3861案例1:日志、线程、定时器中,我们安排了四个小任务,分别介绍了如何搭建Hi3861实验环境、如何打印日志、如何启动线程、如何启动定时器,本文将进一步深入学习Hi3861及其外部设备,通过本文的学习,你需要完成如下三个任务: + +任务一 控制小灯泡的闪烁 + +![](figures/zh-cn_image_0000001235890713.gif) + +任务二 控制红绿灯的闪烁 + +![](figures/zh-cn_image_0000001235651103.gif) + +任务三 通过按键控制红绿灯 + +![](figures/zh-cn_image_0000001189490828.gif) + +# Hi3861开发环境准备 + +完成本篇Codelab,我们首先需要完成开发环境搭建、源码编译,可参照如下步骤进行。 + +1. [搭建开发环境](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-lite-steps-hi3861-setting.md)。 +2. [源码获取](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/get-code/sourcecode-acquire.md):建议开发者选择LTS 3.0版本源码进行编译,本篇Codelab是基于此版本开发的。 + +**您需要使用如下设备完成本Codelab:** + +Hi3861V100开发板主板、底板以及智能红绿灯板。 + +# 相关概念 + +GPIO(英语:General-purpose input/output),通用型之输入输出的简称。H3861芯片内部包括了GPIO模块,用于实现芯片引脚上的数字输入、输出功能。输入、输出的数字表示对应的状态,状态只能是0或1两种,通常使用低电平表示0,高电平表示1。 + +# 任务一 控制小灯泡的闪烁 + +下文将通过修改源码的方式展示如何将Hi3861V100开发板主板上的小灯泡点亮,并控制其闪烁。 + +**1. 确定目录结构**。 + +开发者编写业务时,务必先在./applications/sample/wifi-iot/app路径下新建一个目录(或一套目录结构),用于存放业务源码文件。 + +例如:在app目录下新增业务light,其中main.c为业务代码,BUILD.gn为编译脚本,具体规划目录结构如下: + +``` +. +└── applications + └── sample + └── wifi-iot + └── app + │── light + │ │── main.c + │ └── BUILD.gn + └── BUILD.gn +``` + +**2. 编写业务代码**。 + +在./applications/sample/wifi-iot/app/light目录下新建main.c文件,在main.c中新建业务入口函数Main,并实现业务逻辑。并在代码最下方,使用OpenHarmony启动恢复模块接口SYS\_RUN\(\)启动业务。(SYS\_RUN定义在ohos\_init.h文件中) + +``` +#include +#include +#include + +#include "ohos_init.h" +#include "cmsis_os2.h" +#include "iot_gpio.h" + +#define LED_INTERVAL_TIME_US(1 * 1000000) +#define LED_TASK_STACK_SIZE 512 +#define LED_TEST_GPIO_9 9 + +// 小灯泡每隔1秒闪烁一次 +static void SparkTask(void) +{ + IoTGpioInit(LED_TEST_GPIO_9); + hi_io_set_func(LED_TEST_GPIO_9, 0); + IoTGpioSetDir(LED_TEST_GPIO_9, IOT_GPIO_DIR_OUT); + + while (1) { + IoTGpioSetOutputVal(LED_TEST_GPIO_9, 1); + usleep(LED_INTERVAL_TIME_US); + IoTGpioSetOutputVal(LED_TEST_GPIO_9, 0); + usleep(LED_INTERVAL_TIME_US); + } +} + +static void Main(void) +{ + // 指定线程的属性 + osThreadAttr_t attr; + attr.name = "SparkTask"; + attr.attr_bits = 0U; + attr.cb_mem = NULL; + attr.cb_size = 0U; + attr.stack_mem = NULL; + attr.stack_size = LED_TASK_STACK_SIZE; + attr.priority = osPriorityNormal; + + if (osThreadNew((osThreadFunc_t)SparkTask, NULL, &attr) == NULL) { + printf("Failed to create SparkTask!\r\n"); + } + +} + +SYS_RUN(Main); +``` + +>![](public_sys-resources/icon-note.gif) **说明:** +>Hi3861V100开发板主板上的小灯泡是与GPIO 9号引脚进行的连接,故控制GPIO 9的高低电平就可以控制小灯泡的闪烁。 + +**3.编写用于将业务构建成静态库的BUILD.gn文件**。 + +在./applications/sample/wifi-iot/app/light目录下新建BUILD.gn文件,并完成如下配置。 + +``` +static_library("main") { + sources = [ + "main.c" + ] + include_dirs = [ + "//utils/native/lite/include" + ] +} +``` + +**4. 编写模块BUILD.gn文件,指定需参与构建的特性模块**。 + +配置./applications/sample/wifi-iot/app/BUILD.gn文件,在features字段中增加索引,使目标模块参与编译。features字段指定业务模块的路径和目标,features字段配置如下。 + +``` +import("//build/lite/config/component/lite_component.gni") + +lite_component("app") { + features = [ + "light:main", + ] +} +``` + +**5. 代码编译**。 + +依次点击图标1和2中的两个按钮,等待代码编译,提示\[SUCCESS\]则表示项目编译成功。 + +![](figures/zh-cn_image_0000001233826721.png) + +**6. [烧录](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-lite-steps-hi3861-burn.md)**。 + +**7. 运行结果**。 + +示例代码编译、烧录、运行、调测后,重启开发板后,Hi3861V100开发板主板上的小灯泡点亮将被点亮,并呈现每个1秒闪烁一次的效果,效果如下所示: + +![](figures/zh-cn_image_0000001235170729.gif) + +# 任务二 控制红绿灯的闪烁 + +下文将通过修改源码的方式展示如何将红绿灯开发板点亮,并控制每隔1秒钟切换一个信号灯。 + +**1. 确定目录结构**。 + +开发者编写业务时,务必先在./applications/sample/wifi-iot/app路径下新建一个目录(或一套目录结构),用于存放业务源码文件。 + +例如:在app目录下新增业务traffic\_light,其中main.c为业务代码,BUILD.gn为编译脚本,具体规划目录结构如下: + +``` +. +└── applications + └── sample + └── wifi-iot + └── app + │── traffic_light + │ │── main.c + │ └── BUILD.gn + └── BUILD.gn +``` + +**2. 编写业务代码**。 + +在./applications/sample/wifi-iot/app/traffic\_light目录下新建main.c文件,在main.c中新建业务入口函数Main,并实现业务逻辑。并在代码最下方,使用OpenHarmony启动恢复模块接口SYS\_RUN\(\)启动业务。(SYS\_RUN定义在ohos\_init.h文件中)。 + +``` +#include +#include +#include + +#include "ohos_init.h" +#include "cmsis_os2.h" +#include "iot_gpio.h" + +#define TASK_STACK_SIZE (1024 * 4) +#define TASK_SLEEP_TIME (1 * 1000 * 1000) + +#define LED_TEST_GPIO_10 10 +#define LED_TEST_GPIO_11 11 +#define LED_TEST_GPIO_12 12 + +// 初始化红绿灯 +static void InitTrafficLight(void) +{ + IoTGpioInit(LED_TEST_GPIO_10); + hi_io_set_func(LED_TEST_GPIO_10, 0); + IoTGpioSetDir(LED_TEST_GPIO_10, IOT_GPIO_DIR_OUT); + + IoTGpioInit(LED_TEST_GPIO_11); + hi_io_set_func(LED_TEST_GPIO_11, 0); + IoTGpioSetDir(LED_TEST_GPIO_11, IOT_GPIO_DIR_OUT); + + IoTGpioInit(LED_TEST_GPIO_12); + hi_io_set_func(LED_TEST_GPIO_12, 0); + IoTGpioSetDir(LED_TEST_GPIO_12, IOT_GPIO_DIR_OUT); +} + +static void TrafficLightTask(void) +{ + InitTrafficLight(); + + while (1) { + IoTGpioSetOutputVal(LED_TEST_GPIO_10, 1); + IoTGpioSetOutputVal(LED_TEST_GPIO_11, 0); + IoTGpioSetOutputVal(LED_TEST_GPIO_12, 0); + usleep(TASK_SLEEP_TIME); + + IoTGpioSetOutputVal(LED_TEST_GPIO_10, 0); + IoTGpioSetOutputVal(LED_TEST_GPIO_11, 1); + IoTGpioSetOutputVal(LED_TEST_GPIO_12, 0); + usleep(TASK_SLEEP_TIME); + + IoTGpioSetOutputVal(LED_TEST_GPIO_10, 0); + IoTGpioSetOutputVal(LED_TEST_GPIO_11, 0); + IoTGpioSetOutputVal(LED_TEST_GPIO_12, 1); + usleep(TASK_SLEEP_TIME); + } +} + +static void Main(void) +{ + // 指定线程的属性 + osThreadAttr_t attr; + attr.name = "TrafficLightTask"; + attr.attr_bits = 0U; + attr.cb_mem = NULL; + attr.cb_size = 0U; + attr.stack_mem = NULL; + attr.stack_size = TASK_STACK_SIZE; + attr.priority = osPriorityNormal; + + if (osThreadNew((osThreadFunc_t)TrafficLightTask, NULL, &attr) == NULL) { + printf("Failed to create TrafficLightTask!\r\n"); + } +} + +SYS_RUN(Main); +``` + +>![](public_sys-resources/icon-note.gif) **说明:** +>红绿灯开发板上红、黄、绿三个小灯泡分别与GPIO引脚10、11、12相连接。 + +**3.编写用于将业务构建成静态库的BUILD.gn文件**。 + +在./applications/sample/wifi-iot/app/light目录下新建BUILD.gn文件,并完成如下配置。 + +``` +static_library("main") { + sources = [ + "main.c" + ] + include_dirs = [ + "//utils/native/lite/include" + ] +} +``` + +**4. 编写模块BUILD.gn文件,指定需参与构建的特性模块**。 + +配置./applications/sample/wifi-iot/app/BUILD.gn文件,在features字段中增加索引,使目标模块参与编译。features字段指定业务模块的路径和目标,features字段配置如下。 + +``` +import("//build/lite/config/component/lite_component.gni") + +lite_component("app") { + features = [ + "traffic_light:main", + ] +} +``` + +**5. 代码编译**。 + +依次点击图标1和2中的两个按钮,等待代码编译,提示\[SUCCESS\]则表示项目编译成功。 + +![](figures/zh-cn_image_0000001196059582.png) + +**6. [烧录](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-lite-steps-hi3861-burn.md)**。 + +**7. 运行结果**。 + +示例代码编译、烧录、运行、调测后,重启开发板后,Hi3861V100开发板主板上的红绿灯开发板将会被点亮,并每隔1秒钟切换一个信号灯,效果如下所示: + +![](figures/zh-cn_image_0000001190491172.gif) + +# 任务三 通过按键控制红绿灯 + +下文将通过修改源码的方式展示如何通过按钮的点击事件,实现控制红绿灯开发板上小灯的交替变化。 + +**1. 确定目录结构**。 + +开发者编写业务时,务必先在./applications/sample/wifi-iot/app路径下新建一个目录(或一套目录结构),用于存放业务源码文件。 + +例如:在app目录下新增业务light,其中main.c为业务代码,BUILD.gn为编译脚本,具体规划目录结构如下: + +``` +. +└── applications + └── sample + └── wifi-iot + └── app + │── traffic_light_button + │ │── main.c + │ └── BUILD.gn + └── BUILD.gn +``` + +**2. 编写业务代码**。 + +在./applications/sample/wifi-iot/app/traffic\_light\_button目录下新建main.c文件,在main.c中新建业务入口函数Main,并实现业务逻辑。并在代码最下方,使用OpenHarmony启动恢复模块接口SYS\_RUN\(\)启动业务,(SYS\_RUN定义在ohos\_init.h文件中)。 + +``` +#include +#include +#include + +#include "ohos_init.h" +#include "cmsis_os2.h" +#include "iot_gpio.h" + +#define TASK_STACK_SIZE (1024 * 4) + +#define LED_TEST_GPIO_8 8 +#define LED_TEST_GPIO_10 10 +#define LED_TEST_GPIO_11 11 +#define LED_TEST_GPIO_12 12 +#define NUM_3 3 +#define RED_LED_BRIGHT 1 +#define GREEN_LED_BRIGHT 2 +#define YELLOW_LED_BRIGHT 3 + +static int g_currentBright = 0; + +// 初始化红绿灯 +static void InitTrafficLight(void) +{ + IoTGpioInit(LED_TEST_GPIO_10); + hi_io_set_func(LED_TEST_GPIO_10, 0); + IoTGpioSetDir(LED_TEST_GPIO_10, IOT_GPIO_DIR_OUT); + + IoTGpioInit(LED_TEST_GPIO_11); + hi_io_set_func(LED_TEST_GPIO_11, 0); + IoTGpioSetDir(LED_TEST_GPIO_11, IOT_GPIO_DIR_OUT); + + IoTGpioInit(LED_TEST_GPIO_12); + hi_io_set_func(LED_TEST_GPIO_12, 0); + IoTGpioSetDir(LED_TEST_GPIO_12, IOT_GPIO_DIR_OUT); +} + +// 按键每按下一次,currentBright加1 +static void OnButtonPressed(char * arg) +{ + (void)arg; + g_currentBright++; +} + +// 初始化按钮 +static void InitButton(void) +{ + IoTGpioInit(LED_TEST_GPIO_8); + hi_io_set_func(LED_TEST_GPIO_8, 0); + IoTGpioSetDir(LED_TEST_GPIO_8, IOT_GPIO_DIR_IN); + hi_io_set_pull(LED_TEST_GPIO_8, 1); + IoTGpioRegisterIsrFunc(LED_TEST_GPIO_8, IOT_INT_TYPE_EDGE, IOT_GPIO_EDGE_FALL_LEVEL_LOW, + OnButtonPressed, NULL); +} + +static void TrafficLightTask(void) +{ + InitTrafficLight(); + + InitButton(); + + while (1) { + switch (g_currentBright % NUM_3) { + case RED_LED_BRIGHT: + IoTGpioSetOutputVal(LED_TEST_GPIO_10, 1); + IoTGpioSetOutputVal(LED_TEST_GPIO_11, 0); + IoTGpioSetOutputVal(LED_TEST_GPIO_12, 0); + break; + case GREEN_LED_BRIGHT: + IoTGpioSetOutputVal(LED_TEST_GPIO_10, 0); + IoTGpioSetOutputVal(LED_TEST_GPIO_11, 1); + IoTGpioSetOutputVal(LED_TEST_GPIO_12, 0); + break; + case YELLOW_LED_BRIGHT: + IoTGpioSetOutputVal(LED_TEST_GPIO_10, 0); + IoTGpioSetOutputVal(LED_TEST_GPIO_11, 0); + IoTGpioSetOutputVal(LED_TEST_GPIO_12, 1); + break; + default: + IoTGpioSetOutputVal(LED_TEST_GPIO_10, 0); + IoTGpioSetOutputVal(LED_TEST_GPIO_11, 0); + IoTGpioSetOutputVal(LED_TEST_GPIO_12, 1); + break; + } + } +} + +static void Main(void) +{ + // 指定线程的属性 + osThreadAttr_t attr; + attr.name = "TrafficLightTask"; + attr.attr_bits = 0U; + attr.cb_mem = NULL; + attr.cb_size = 0U; + attr.stack_mem = NULL; + attr.stack_size = TASK_STACK_SIZE; + attr.priority = osPriorityNormal; + + if (osThreadNew((osThreadFunc_t)TrafficLightTask, NULL, &attr) == NULL) { + printf("Failed to create TrafficLightTask!\r\n"); + } + +} + +SYS_RUN(Main); +``` + +>![](public_sys-resources/icon-note.gif) **说明:** +>红绿灯开发板上红、黄、绿三个小灯泡分别接到了GPIO引脚10、11、12,按键接到的GPIO引脚为8。 + +**3.编写用于将业务构建成静态库的BUILD.gn文件**。 + +在./applications/sample/wifi-iot/app/traffic\_light\_button目录下新建BUILD.gn文件,并完成如下配置。 + +``` +static_library("main") { + sources = [ + "main.c" + ] + include_dirs = [ + "//utils/native/lite/include" + ] +} +``` + +**4. 编写模块BUILD.gn文件,指定需参与构建的特性模块。** + +配置./applications/sample/wifi-iot/app/BUILD.gn文件,在features字段中增加索引,使目标模块参与编译。features字段指定业务模块的路径和目标,features字段配置如下。 + +``` +import("//build/lite/config/component/lite_component.gni") + +lite_component("app") { + features = [ + "traffic_light_button:main", + ] +} +``` + +**5. 代码编译**。 + +依次点击图标1和2中的两个按钮,等待代码编译,提示\[SUCCESS\]则表示项目编译成功。 + +![](figures/zh-cn_image_0000001196379400.png) + +**6. [烧录](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-lite-steps-hi3861-burn.md)**。 + +**7. 运行结果**。 + +示例代码编译、烧录、运行、调测后,重启开发板后,通过按钮的点击事件,即可实现控制红绿灯开发板上小灯的交替变化,效果如下所示: + +![](figures/zh-cn_image_0000001190490784.gif) + +# 恭喜你 + +目前您已经成功完成了本Codelab,并且学到了: + +- GPIO高低电平的设置 +- 通过GPIO高低电平,控制小灯泡、控制红绿灯的闪烁 +- 按钮事件的检测 + diff --git "a/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001189490828.gif" "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001189490828.gif" new file mode 100644 index 0000000000000000000000000000000000000000..f8b9567f28141b4d95e0fb9a1473f30ff992f7e5 Binary files /dev/null and "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001189490828.gif" differ diff --git "a/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001190490784.gif" "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001190490784.gif" new file mode 100644 index 0000000000000000000000000000000000000000..f8b9567f28141b4d95e0fb9a1473f30ff992f7e5 Binary files /dev/null and "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001190490784.gif" differ diff --git "a/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001190491172.gif" "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001190491172.gif" new file mode 100644 index 0000000000000000000000000000000000000000..d6ebadb7e56a9cbda99b87a8dd91e0e382dc6e74 Binary files /dev/null and "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001190491172.gif" differ diff --git "a/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001196059582.png" "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001196059582.png" new file mode 100644 index 0000000000000000000000000000000000000000..d68d1312c3f1d7ee3b7a33d592fcf208d3ac219f Binary files /dev/null and "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001196059582.png" differ diff --git "a/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001196379400.png" "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001196379400.png" new file mode 100644 index 0000000000000000000000000000000000000000..d68d1312c3f1d7ee3b7a33d592fcf208d3ac219f Binary files /dev/null and "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001196379400.png" differ diff --git "a/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001233826721.png" "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001233826721.png" new file mode 100644 index 0000000000000000000000000000000000000000..d68d1312c3f1d7ee3b7a33d592fcf208d3ac219f Binary files /dev/null and "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001233826721.png" differ diff --git "a/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001235170729.gif" "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001235170729.gif" new file mode 100644 index 0000000000000000000000000000000000000000..28baf8fe217814020e3f704b4a303f062119f85b Binary files /dev/null and "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001235170729.gif" differ diff --git "a/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001235651103.gif" "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001235651103.gif" new file mode 100644 index 0000000000000000000000000000000000000000..d6ebadb7e56a9cbda99b87a8dd91e0e382dc6e74 Binary files /dev/null and "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001235651103.gif" differ diff --git "a/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001235890713.gif" "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001235890713.gif" new file mode 100644 index 0000000000000000000000000000000000000000..28baf8fe217814020e3f704b4a303f062119f85b Binary files /dev/null and "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/figures/zh-cn_image_0000001235890713.gif" differ diff --git "a/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-caution.gif" "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-caution.gif" new file mode 100644 index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571 Binary files /dev/null and "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-caution.gif" differ diff --git "a/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-danger.gif" "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-danger.gif" new file mode 100644 index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571 Binary files /dev/null and "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-danger.gif" differ diff --git "a/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-note.gif" "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-note.gif" new file mode 100644 index 0000000000000000000000000000000000000000..6314297e45c1de184204098efd4814d6dc8b1cda Binary files /dev/null and "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-note.gif" differ diff --git "a/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-notice.gif" "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-notice.gif" new file mode 100644 index 0000000000000000000000000000000000000000..86024f61b691400bea99e5b1f506d9d9aef36e27 Binary files /dev/null and "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-notice.gif" differ diff --git "a/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-tip.gif" "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-tip.gif" new file mode 100644 index 0000000000000000000000000000000000000000..93aa72053b510e456b149f36a0972703ea9999b7 Binary files /dev/null and "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-tip.gif" differ diff --git "a/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-warning.gif" "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-warning.gif" new file mode 100644 index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571 Binary files /dev/null and "b/Device/\345\210\251\347\224\250GPIO\347\202\271\344\272\256\345\260\217\347\201\257\346\263\241\343\200\201\347\272\242\347\273\277\347\201\257/public_sys-resources/icon-warning.gif" differ diff --git "a/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/README.md" "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/README.md" new file mode 100644 index 0000000000000000000000000000000000000000..b74739b854dfbb023087551e7f485f542259d4e9 --- /dev/null +++ "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/README.md" @@ -0,0 +1,375 @@ +# 任务介绍 + +PWM是脉冲宽度调制(Pulse Width Modulation)的缩写,是一种对模拟信号电平进行数字编码并将其转换为脉冲的技术。常用于马达控制、背光亮度调节等。 + +本篇Codelab将通过以下两个简单的样例,让开发者熟悉OpenHarmony PWM相关API的使用: + +使用PWM调节呼吸灯亮暗,效果如下: +![](figures/zh-cn_attachment_0000001192336252.gif) + + + +# Hi3861开发环境准备 + +完成本篇Codelab,我们首先需要完成开发环境搭建、源码编译,可参照如下步骤进行。 + +1. [搭建开发环境](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-lite-steps-hi3861-setting.md)。 +2. [源码获取](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/get-code/sourcecode-acquire.md):建议开发者选择LTS 3.0版本源码进行编译,本篇Codelab是基于此版本开发的。 + +**您需要使用如下设备完成本Codelab:** + +Hi3861V100开发板主板、底板以及智能红绿灯板。 + +# 相关概念 + +**PWM API** + + + + + + + + + + + + + + + + + + + +

方法

+

说明

+

IoTPwmInit (unsigned int port)

+

初始化PWM设备。

+

IoTPwmDeinit (unsigned int port)

+

去初始化PWM设备。

+

IoTPwmStart (unsigned int port, unsigned short duty, unsigned int freq)

+

根据给定的输出频率和占空比,从指定端口启动PWM信号输出。

+

IoTPwmStop (unsigned int port)

+

停止指定端口的PWM信号输出。

+
+ +**Hi3861V100开发板说明** + +实现IOT外设控制,首先需要通过查阅原理图明确接线关系。经过查阅,智能红绿灯板与主控芯片(Pegasus)引脚的对应关系如下: + + + + + + + + + + + + + + + + +

模块

+

控制管脚

+

PWM

+

蜂鸣器

+

GPIO9

+

PWM0

+

红灯

+

GPIO10

+

PWM1

+
+ +>![](public_sys-resources/icon-note.gif) **说明:** +>开发板原理图,请开发者联系Hi3861购买渠道客服获取。 + +# 任务一 使用PWM调节呼吸灯灯亮暗 + +本节介绍如何使用PWM API,实现对GPIO的控制,达到呼吸灯亮度逐渐变化的效果(逐渐变暗和逐渐变亮)。 + +1. **确定目录结构**。 + + 开发者编写业务时,务必先在./applications/sample/wifi-iot/app路径下新建一个目录(或一套目录结构),用于存放业务源码文件。 + + 在app下新增业务pwmled,其中pwm\_led.c为业务代码,BUILD.gn为编译脚本,具体规划目录结构如下: + + ``` + . + └── applications + └── sample + └── wifi-iot + └── app + │── pwmled + │ │── pwm_led.c + │ └── BUILD.gn + └── BUILD.gn + ``` + +2. **使PWM功能生效**。 + + 打开./device/hisilicon/hispark\_pegasus/sdk\_liteos/build/config/usr\_config.mk文件,找到CONFIG\_PWM\_SUPPORT is not set,取消注释,并将其修改为CONFIG\_PWM\_SUPPORT=y。 + + ![](figures/zh-cn_image_0000001233802341.png) + +3. **编写业务代码。** + + 新建./applications/sample/wifi-iot/app/pwmled下的pwm\_led.c文件,在pwm\_led.c中新建业务入口函数PWMLedExample,并实现业务逻辑。并在代码最下方,使用OpenHarmony启动恢复模块接口APP\_FEATURE\_INIT\(\)启动业务。 + + ``` + #include + #include + #include + + #include "ohos_init.h" + #include "cmsis_os2.h" + #include "iot_gpio.h" + #include "iot_pwm.h" + #include "hi_io.h" + #include "hi_pwm.h" + + #define TASK_STACK_SIZE 512 + // PWM输出占空比 + #define PVM_OUT_DUTY 50 + // PWM输出频率 + #define PVM_OUT_FREQ 10000 + #define TASK_SLEEP_TIME (0.03 * 1000 * 1000) + + static void PWMLedTask(void *arg) + { + (void)arg; + + while (1) { + // 逐步增加PVM_OUT_DUTY 值使LED灯逐渐变亮 + for (int i = 0; i < PVM_OUT_DUTY; i++) { + // 启动PWM信号输出 + IoTPwmStart(HI_PWM_PORT_PWM1, i, PVM_OUT_FREQ); + usleep(TASK_SLEEP_TIME); + // 停止PWM信号输出 + IoTPwmStop(HI_PWM_PORT_PWM1); + } + // 逐步减小PVM_OUT_DUTY 值使LED灯逐渐变暗 + for (int i = PVM_OUT_DUTY; i > 0; i--) { + // 启动PWM信号输出 + IoTPwmStart(HI_PWM_PORT_PWM1, i, PVM_OUT_FREQ); + usleep(TASK_SLEEP_TIME); + // 停止PWM信号输出 + IoTPwmStop(HI_PWM_PORT_PWM1); + } + } + } + + static void PWMLedExample(void) + { + osThreadAttr_t attr; + + // 初始化10号管脚(红色led灯) + IoTGpioInit(HI_IO_NAME_GPIO_10); + + // 将10号管脚设置为PWM功能 + hi_io_set_func(HI_IO_NAME_GPIO_10, HI_IO_FUNC_GPIO_10_PWM1_OUT); + + // 初始化PWM设备 + IoTPwmInit(HI_PWM_PORT_PWM1); + + attr.name = "PWMLedTask"; + attr.attr_bits = 0U; + attr.cb_mem = NULL; + attr.cb_size = 0U; + attr.stack_mem = NULL; + attr.stack_size = TASK_STACK_SIZE; + attr.priority = osPriorityNormal; + + if (osThreadNew(PWMLedTask, NULL, &attr) == NULL) { + printf("[PWMLedExample] Failed to create PWMLedTask!\n"); + } + } + + APP_FEATURE_INIT(PWMLedExample); + ``` + +4. **编写用于将业务构建成静态库的BUILD.gn文件。** + + 新建./applications/sample/wifi-iot/app/pwmled下的BUILD.gn文件,并完成如下配置。 + + ``` + static_library("pwm_led") { + sources = [ + "pwm_led.c" + ] + + include_dirs = [ + "//utils/native/lite/include", + "//kernel/liteos_m/kal/cmsis", + "//base/iot_hardware/peripheral/interfaces/kits", + ] + } + ``` + + - static\_library中指定业务模块的编译结果,开发者根据实际情况完成填写。 + - sources中指定静态库.a所依赖的.c文件及其路径,若路径中包含"//"则表示绝对路径(此处为代码根路径),若不包含"//"则表示相对路径。 + - include\_dirs中指定source所需要依赖的.h文件路径。 + +5. **编写模块BUILD.gn文件,指定需参与构建的特性模块。** + + 配置./applications/sample/wifi-iot/app/BUILD.gn文件,在features字段中增加索引,使目标模块参与编译。features字段指定业务模块的路径和目标,features字段配置如下。 + + ``` + import("//build/lite/config/component/lite_component.gni") + + lite_component("app") { + features = [ + "pwmled:pwm_led", + ] + } + ``` + +6. **代码编译和烧录**。 + + 代码编译和烧录可以参考: + + - [源码编译](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-lite-steps-hi3861-building.md) + - [烧录](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-lite-steps-hi3861-burn.md) + + 完成烧录后,按下RST键复位模组,可发现呼吸灯亮度在周期性的逐渐变亮和变暗。 + + +# 任务二 使用PWM控制蜂鸣器 + +本节介绍如何使用PWM API,实现对GPIO的控制,使蜂鸣器持续鸣叫。 + +1. **确定目录结构**。 + + 开发者编写业务时,务必先在./applications/sample/wifi-iot/app路径下新建一个目录(或一套目录结构),用于存放业务源码文件。 + + 在app下新增业务pwmbeer,其中pwm\_beer.c为业务代码,BUILD.gn为编译脚本,具体规划目录结构如下: + + ``` + . + └── applications + └── sample + └── wifi-iot + └── app + │── pwmbeer + │ │── pwm_beer.c + │ └── BUILD.gn + └── BUILD.gn + ``` + +2. **编写业务代码**。 + + 新建./applications/sample/wifi-iot/app/pwmbeer下的pwm\_beer.c文件,在pwm\_beer.c中新建业务入口函数PWMBeerExample,并实现业务逻辑。并在代码最下方,使用OpenHarmony启动恢复模块接口APP\_FEATURE\_INIT\(\)启动业务。 + + ``` + #include + #include "ohos_init.h" + #include "cmsis_os2.h" + #include "hi_gpio.h" + #include "hi_io.h" + #include "hi_pwm.h" + + #define TASK_STACK_SIZE 512 + // PWM输出占空比 + #define PVM_OUT_DUTY 50 + // PWM输出频率 + #define PVM_OUT_FREQ 10000 + #define TASK_SLEEP_TIME (0.03 * 1000 * 1000) + + static void PWMBeerTask(void * arg) + { + (void)arg; + + while (1) { + // 启动PWM信号输出 + IoTPwmStart(HI_PWM_PORT_PWM0, PVM_OUT_DUTY, PVM_OUT_FREQ); + usleep(TASK_SLEEP_TIME); + // 停止PWM信号输出 + IoTPwmStop(HI_PWM_PORT_PWM0); + } + } + + static void PWMBeerExample(void) + { + osThreadAttr_t attr; + + // 初始化9号管脚(蜂鸣器) + IoTGpioInit(HI_IO_NAME_GPIO_9); + + // 将9号管脚设置为PWM功能,用于控制蜂鸣器管脚 + hi_io_set_func(HI_IO_NAME_GPIO_9, HI_IO_FUNC_GPIO_9_PWM0_OUT); + + // 初始化PWM设备 + IoTPwmInit(HI_PWM_PORT_PWM0); + + attr.name = "PWMBeerTask"; + attr.attr_bits = 0U; + attr.cb_mem = NULL; + attr.cb_size = 0U; + attr.stack_mem = NULL; + attr.stack_size = TASK_STACK_SIZE; + attr.priority = osPriorityNormal; + + if (osThreadNew(PWMBeerTask, NULL, &attr) == NULL) { + printf("[PWMBeerTask] Failed to create PWMBeerTask!\n"); + } + } + + APP_FEATURE_INIT(PWMBeerExample); + ``` + +3. **编写用于将业务构建成静态库的BUILD.gn文件**。 + + 新建./applications/sample/wifi-iot/app/pwmbeer下的BUILD.gn文件,并完成如下配置。 + + ``` + static_library("pwm_beer") { + sources = [ + "pwm_beer.c" + ] + + include_dirs = [ + "//utils/native/lite/include", + "//kernel/liteos_m/kal/cmsis", + "//base/iot_hardware/peripheral/interfaces/kits", + ] + } + ``` + + - static\_library中指定业务模块的编译结果,开发者根据实际情况完成填写。 + - sources中指定静态库.a所依赖的.c文件及其路径,若路径中包含"//"则表示绝对路径(此处为代码根路径),若不包含"//"则表示相对路径。 + - include\_dirs中指定source所需要依赖的.h文件路径。 + +4. **编写模块BUILD.gn文件,指定需参与构建的特性模块。** + + 配置./applications/sample/wifi-iot/app/BUILD.gn文件,在features字段中增加索引,使目标模块参与编译。features字段指定业务模块的路径和目标,features字段配置如下。 + + ``` + import("//build/lite/config/component/lite_component.gni") + + lite_component("app") { + features = [ + "pwmbeer:pwm_beer", + ] + } + ``` + +5. **代码编译和烧录**。 + + 代码编译和烧录可以参考: + + - [源码编译](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-lite-steps-hi3861-building.md) + - [烧录](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-lite-steps-hi3861-burn.md) + + 完成烧录后,按下RST键复位模组,可发现蜂鸣器持续鸣叫。 + + +# 恭喜你 + +目前您已经成功完成了本Codelab,并且学到了: + +- 如何使用PWM控制LED灯。 +- 如何使用PWM控制蜂鸣器。 + diff --git "a/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/figures/zh-cn_attachment_0000001192336252.gif" "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/figures/zh-cn_attachment_0000001192336252.gif" new file mode 100644 index 0000000000000000000000000000000000000000..80bac4259a9e8929c8c63ed7740430d271de5d51 Binary files /dev/null and "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/figures/zh-cn_attachment_0000001192336252.gif" differ diff --git "a/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/figures/zh-cn_image_0000001233802341.png" "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/figures/zh-cn_image_0000001233802341.png" new file mode 100644 index 0000000000000000000000000000000000000000..a30751f2ca6f4501369544f1969a6a112b9d4465 Binary files /dev/null and "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/figures/zh-cn_image_0000001233802341.png" differ diff --git "a/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-caution.gif" "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-caution.gif" new file mode 100644 index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571 Binary files /dev/null and "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-caution.gif" differ diff --git "a/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-danger.gif" "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-danger.gif" new file mode 100644 index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571 Binary files /dev/null and "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-danger.gif" differ diff --git "a/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-note.gif" "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-note.gif" new file mode 100644 index 0000000000000000000000000000000000000000..6314297e45c1de184204098efd4814d6dc8b1cda Binary files /dev/null and "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-note.gif" differ diff --git "a/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-notice.gif" "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-notice.gif" new file mode 100644 index 0000000000000000000000000000000000000000..86024f61b691400bea99e5b1f506d9d9aef36e27 Binary files /dev/null and "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-notice.gif" differ diff --git "a/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-tip.gif" "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-tip.gif" new file mode 100644 index 0000000000000000000000000000000000000000..93aa72053b510e456b149f36a0972703ea9999b7 Binary files /dev/null and "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-tip.gif" differ diff --git "a/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-warning.gif" "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-warning.gif" new file mode 100644 index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571 Binary files /dev/null and "b/Device/\345\210\251\347\224\250PWM\346\226\271\346\263\242\346\216\247\345\210\266\345\221\274\345\220\270\347\201\257\345\222\214\350\234\202\351\270\243\345\231\250/public_sys-resources/icon-warning.gif" differ diff --git "a/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/README.md" "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/README.md" new file mode 100644 index 0000000000000000000000000000000000000000..f9ca7692ef6198d9210ffcc82554446d9158f621 --- /dev/null +++ "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/README.md" @@ -0,0 +1,413 @@ +# 任务介绍 + +OpenHarmony轻量和小型系统适用于内存较小的IOT设备。通过本文,开发者可以快速熟悉OpenHarmony轻量和小型系统的环境搭建、编译、烧录、调测以及打印“Hello World”日志、启动一个线程、启动一个定时器等。本文选取的开发板为Hi3861 WLAN模组,Hi3861开发板是一片大约2cm\*5cm大小的开发板,是一款高度集成的2.4GHz WLAN SoC芯片,集成IEEE 802.11b/g/n基带和RF(Radio Frequency)电路。支持OpenHarmony,并配套提供开放、易用的开发和调试运行环境。通过本文的学习,您需要完成如下四个任务: + +- 任务一 Hi3861环境搭建 +- 任务二 如何打印日志 +- 任务三 如何启动线程 +- 任务四 如何启动定时器 + +# Hi3861开发环境准备 + +完成本篇Codelab,我们首先需要完成开发环境搭建、源码编译,可参照如下步骤进行。 + +1. [搭建开发环境](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-lite-steps-hi3861-setting.md)。 +2. [源码获取](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/get-code/sourcecode-acquire.md):建议开发者选择LTS 3.0版本源码进行编译,本篇Codelab是基于此版本开发的。 + +**您需要使用如下设备完成本Codelab:** + +Hi3861V100开发板主板 + +>![](public_sys-resources/icon-note.gif) **说明:** +>请严格按照上文步骤1-3中的指导文档进行环境搭建,如遇问题可以前往[开发者论坛](https://developer.huawei.com/consumer/cn/forum/block/device)进行求助(搜索关键字Hi3861)。 + +# 任务一 如何打印日志 + +下文将通过修改源码的方式展示如何编写简单程序,输出“Hello world”。 + +**1. 确定目录结构**。 + +开发者编写业务时,务必先在./applications/sample/wifi-iot/app路径下新建一个目录(或一套目录结构),用于存放业务源码文件。 + +例如:在app目录下新增业务helloworld,其中main.c为业务代码,BUILD.gn为编译脚本,具体规划目录结构如下: + +``` +. +└── applications + └── sample + └── wifi-iot + └── app + │── helloworld + │ │── main.c + │ └── BUILD.gn + └── BUILD.gn +``` + +**2. 编写业务代码**。 + +在./applications/sample/wifi-iot/app/helloworld目录下新建main.c文件,在main.c中新建业务入口函数HelloWorld,并实现业务逻辑。并在代码最下方,使用OpenHarmony启动恢复模块接口SYS\_RUN\(\)启动业务。(SYS\_RUN定义在ohos\_init.h文件中) + +``` +#include +#include "ohos_init.h" +#include "ohos_types.h" + +void HelloWorld(void) +{ + printf("[DEMO] Hello world.\n"); +} +SYS_RUN(HelloWorld); +``` + +**3.编写用于将业务构建成静态库的BUILD.gn文件**。 + +在./applications/sample/wifi-iot/app/helloworld目录下新建BUILD.gn文件,并完成如下配置。 + +``` +static_library("main") { + sources = [ + "main.c" + ] + include_dirs = [ + "//utils/native/lite/include" + ] +} +``` + +- static\_library中指定业务模块的编译结果,为静态库文件libmyapp.a,开发者根据实际情况完成填写。 +- sources中指定静态库.a所依赖的.c文件及其路径,若路径中包含"//"则表示绝对路径(此处为代码根路径),若不包含"//"则表示相对路径。 +- include\_dirs中指定source所需要依赖的.h文件路径。 + +**4. 编写模块BUILD.gn文件,指定需参与构建的特性模块**。 + +配置./applications/sample/wifi-iot/app/BUILD.gn文件,在features字段中增加索引,使目标模块参与编译。features字段指定业务模块的路径和目标,以helloworld举例,features字段配置如下。 + +``` +import("//build/lite/config/component/lite_component.gni") + +lite_component("app") { + features = [ + "helloworld:main", + ] +} +``` + +- helloworld是相对路径,指向./applications/sample/wifi-iot/app/helloworld/BUILD.gn。 +- main是目标,指向./applications/sample/wifi-iot/app/helloworld/BUILD.gn中的static\_library\("main"\)。 + +**5. 代码编译**。 + +依次点击图标1和2中的两个按钮,等待代码编译,提示\[SUCCESS\]则表示项目编译成功。 + +![](figures/zh-cn_image_0000001233898165.png) + +**6. [烧录](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-lite-steps-hi3861-burn.md)**。 + +**7. 运行结果**。 + +示例代码编译、烧录、运行、调测成功后,重启开发板后将自动在界面输出如下结果: + +``` +[DEMO] Hello world. +``` + +# 任务二 如何启动线程 + +下文将通过修改源码的方式展示如何编写简单程序,教会开发者如何开启一个线程,这里要实现开启两个线程,线程1是每隔1秒打印一次数据,线程2是每隔2秒打印一次数据。 + +**1. 确定目录结构**。 + +开发者编写业务时,务必先在./applications/sample/wifi-iot/app路径下新建一个目录(或一套目录结构),用于存放业务源码文件。 + +例如:在app目录下新增业务thread,其中main.c为业务代码,BUILD.gn为编译脚本,具体规划目录结构如下: + +``` +. +└── applications + └── sample + └── wifi-iot + └── app + │── thread + │ │── main.c + │ └── BUILD.gn + └── BUILD.gn +``` + +**2. 编写业务代码**。 + +在./applications/sample/wifi-iot/app/thread目录下新建main.c文件,在main.c中新建业务入口函数ThreadTest,并实现业务逻辑。并在代码最下方,使用OpenHarmony启动恢复模块接口SYS\_RUN\(\)启动业务。(SYS\_RUN定义在ohos\_init.h文件中) + +``` +#include +#include +#include + +#include "ohos_init.h" +#include "cmsis_os2.h" + +#define TASK_STACK_SIZE (1024 * 4) +#define TASK_SLEEP_TIME1 (1 * 1000 * 1000) +#define TASK_SLEEP_TIME2 (2 * 1000 * 1000) + +// 线程1是每隔1秒打印一次数据 +static void Task1(void) +{ + int count = 0; + while (1) { + printf("Task1----%d\r\n", count++); + usleep(TASK_SLEEP_TIME1); + } +} + +// 线程2是每隔2秒打印一次数据 +static void Task2(void) +{ + int count = 0; + while (1) { + printf("Task2----%d\r\n", count++); + usleep(TASK_SLEEP_TIME2); + } +} + +static void ThreadTest(void) +{ + // 指定线程的属性 + osThreadAttr_t attr; + attr.name = "Thread_1"; + attr.attr_bits = 0U; + attr.cb_mem = NULL; + attr.cb_size = 0U; + attr.stack_mem = NULL; + attr.stack_size = TASK_STACK_SIZE; + attr.priority = osPriorityNormal; + + if (osThreadNew((osThreadFunc_t)Task1, NULL, &attr) == NULL) { + printf("Failed to create Task1!\r\n"); + } + + attr.name = "Thread_2"; + + if (osThreadNew((osThreadFunc_t)Task2, NULL, &attr) == NULL) { + printf("Failed to create Task2!\n\n"); + } + +} + +SYS_RUN(ThreadTest); +``` + +**3.编写用于将业务构建成静态库的BUILD.gn文件**。 + +在./applications/sample/wifi-iot/app/thread目录下新建BUILD.gn文件,并完成如下配置。 + +``` +static_library("main") { + sources = [ + "main.c" + ] + include_dirs = [ + "//utils/native/lite/include" + ] +} +``` + +**4. 编写模块BUILD.gn文件,指定需参与构建的特性模块**。 + +配置./applications/sample/wifi-iot/app/BUILD.gn文件,在features字段中增加索引,使目标模块参与编译。features字段指定业务模块的路径和目标,features字段配置如下。 + +``` +import("//build/lite/config/component/lite_component.gni") + +lite_component("app") { + features = [ + "thread:main", + ] +} +``` + +**5. 代码编译**。 + +依次点击图标1和2中的两个按钮,等待代码编译,提示\[SUCCESS\]则表示项目编译成功。 + +![](figures/zh-cn_image_0000001241179609.png) + +**6. [烧录](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-lite-steps-hi3861-burn.md)**。 + +**7. 运行结果**。 + +示例代码编译、烧录、运行、调测后,重启开发板后将自动在界面输出如下结果,其中线程1是每隔1秒打印一次数据,线程2是每隔2秒打印一次数据: + +``` +Task1----0 +Task2----0 +Task1----1 +Task2----1 +Task1----2 +Task1----3 +Task2----2 +Task1----4 +... +Task1----19 +Task2----10 +Task1----20 +Task1----21 +Task2----11 +Task1----22 +``` + +# 任务三 如何启动定时器 + +下文将通过修改源码的方式展示如何编写简单程序,教会开发者如何开启一个定时器,其中定时器1是每隔1秒打印一次数据,定时器2是第10秒的时候打印一次数据并结束。 + +**1. 确定目录结构**。 + +开发者编写业务时,务必先在./applications/sample/wifi-iot/app路径下新建一个目录(或一套目录结构),用于存放业务源码文件。 + +例如:在app目录下新增业务timer,其中main.c为业务代码,BUILD.gn为编译脚本,具体规划目录结构如下: + +``` +. +└── applications + └── sample + └── wifi-iot + └── app + │── timer + │ │── main.c + │ └── BUILD.gn + └── BUILD.gn +``` + +**2. 编写业务代码**。 + +在./applications/sample/wifi-iot/app/timer目录下新建main.c文件,在main.c中新建业务入口函数TimerTest,并实现业务逻辑。并在代码最下方,使用OpenHarmony启动恢复模块接口SYS\_RUN\(\)启动业务。(SYS\_RUN定义在ohos\_init.h文件中) + +``` +#include +#include +#include + +#include "ohos_init.h" +#include "cmsis_os2.h" + +// 重复执行的定时器 +static void TimerRepeatCallback(void *arg) +{ + (void)arg; + printf("[TimerRepeatCallback] timer repeat callback!\r\n"); +} + +// 只执行一次的定时器 +static void TimerOnceCallback(void *arg) +{ + (void)arg; + printf("[TimerOnceCallback] timer once callback!\r\n"); +} + +static void TimerTest(void) +{ + osTimerId_t id1, id2; + uint32_t timerDelay; + osStatus_t status; + + // 启动第一个定时器:每隔一秒打印一次 + id1 = osTimerNew(TimerRepeatCallback, osTimerPeriodic, NULL, NULL); + if (id1 != NULL) { + // Hi3861 1U=10ms,100U=1S + timerDelay = 100U; + + status = osTimerStart(id1, timerDelay); + if (status != osOK) { + // Timer could not be started + printf("timer repeat start failed\r\n"); + } + } + + // 启动第二个定时器:第10秒的时候打印 + id2 = osTimerNew(TimerOnceCallback, osTimerOnce, NULL, NULL); + if (id2 != NULL) { + // Hi3861 1U=10ms,1000U=10S + timerDelay = 1000U; + + status = osTimerStart(id2, timerDelay); + if (status != osOK) { + // Timer could not be started + printf("timer once start failed\r\n"); + } + } +} + +SYS_RUN(TimerTest); +``` + +**3.编写用于将业务构建成静态库的BUILD.gn文件**。 + +在./applications/sample/wifi-iot/app/timer目录下新建BUILD.gn文件,并完成如下配置。 + +``` +static_library("main") { + sources = [ + "main.c" + ] + include_dirs = [ + "//utils/native/lite/include" + ] +} +``` + +**4. 编写模块BUILD.gn文件,指定需参与构建的特性模块。** + +配置./applications/sample/wifi-iot/app/BUILD.gn文件,在features字段中增加索引,使目标模块参与编译。features字段指定业务模块的路径和目标,features字段配置如下。 + +``` +import("//build/lite/config/component/lite_component.gni") + +lite_component("app") { + features = [ + "timer:main", + ] +} +``` + +**5. 代码编译**。 + +依次点击图标1和2中的两个按钮,等待代码编译,提示\[SUCCESS\]则表示项目编译成功。 + +![](figures/zh-cn_image_0000001241299649.png) + +**6. [烧录](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-lite-steps-hi3861-burn.md)**。 + +**7. 运行结果**。 + +示例代码编译、烧录、运行、调测后,重启开发板后将自动在界面输出如下结果,其中定时器1是每隔1秒打印一次数据,定时器2是第10秒的时候打印一次数据并结束: + +``` +[TimerRepeatCallback] timer repeat callback! +[TimerRepeatCallback] timer repeat callback! +[TimerRepeatCallback] timer repeat callback! +[TimerRepeatCallback] timer repeat callback! +[TimerRepeatCallback] timer repeat callback! +[TimerRepeatCallback] timer repeat callback! +[TimerRepeatCallback] timer repeat callback! +[TimerRepeatCallback] timer repeat callback! +[TimerRepeatCallback] timer repeat callback! +[TimerRepeatCallback] timer repeat callback! +[TimerOnceCallback] timer once callback! +[TimerRepeatCallback] timer repeat callback! +[TimerRepeatCallback] timer repeat callback! +[TimerRepeatCallback] timer repeat callback! +[TimerRepeatCallback] timer repeat callback! +[TimerRepeatCallback] timer repeat callback! +[TimerRepeatCallback] timer repeat callback! +... +``` + +# 恭喜你 + +目前您已经成功完成了本Codelab,并且学到了: + +- Hi3861环境搭建 +- 如何打印日志 +- 如何启动线程 +- 如何启动定时器 + + diff --git "a/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/figures/zh-cn_image_0000001233898165.png" "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/figures/zh-cn_image_0000001233898165.png" new file mode 100644 index 0000000000000000000000000000000000000000..d68d1312c3f1d7ee3b7a33d592fcf208d3ac219f Binary files /dev/null and "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/figures/zh-cn_image_0000001233898165.png" differ diff --git "a/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/figures/zh-cn_image_0000001241179609.png" "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/figures/zh-cn_image_0000001241179609.png" new file mode 100644 index 0000000000000000000000000000000000000000..d68d1312c3f1d7ee3b7a33d592fcf208d3ac219f Binary files /dev/null and "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/figures/zh-cn_image_0000001241179609.png" differ diff --git "a/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/figures/zh-cn_image_0000001241299649.png" "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/figures/zh-cn_image_0000001241299649.png" new file mode 100644 index 0000000000000000000000000000000000000000..d68d1312c3f1d7ee3b7a33d592fcf208d3ac219f Binary files /dev/null and "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/figures/zh-cn_image_0000001241299649.png" differ diff --git "a/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-caution.gif" "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-caution.gif" new file mode 100644 index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571 Binary files /dev/null and "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-caution.gif" differ diff --git "a/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-danger.gif" "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-danger.gif" new file mode 100644 index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571 Binary files /dev/null and "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-danger.gif" differ diff --git "a/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-note.gif" "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-note.gif" new file mode 100644 index 0000000000000000000000000000000000000000..6314297e45c1de184204098efd4814d6dc8b1cda Binary files /dev/null and "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-note.gif" differ diff --git "a/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-notice.gif" "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-notice.gif" new file mode 100644 index 0000000000000000000000000000000000000000..86024f61b691400bea99e5b1f506d9d9aef36e27 Binary files /dev/null and "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-notice.gif" differ diff --git "a/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-tip.gif" "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-tip.gif" new file mode 100644 index 0000000000000000000000000000000000000000..93aa72053b510e456b149f36a0972703ea9999b7 Binary files /dev/null and "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-tip.gif" differ diff --git "a/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-warning.gif" "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-warning.gif" new file mode 100644 index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571 Binary files /dev/null and "b/Device/\346\227\245\345\277\227\343\200\201\347\272\277\347\250\213\343\200\201\345\256\232\346\227\266\345\231\250/public_sys-resources/icon-warning.gif" differ diff --git a/Distributed/DistributeDatabaseDrawEts/README.md b/Distributed/DistributeDatabaseDrawEts/README.md index fbd15b4a062e46e1e253e168a2a6f08e1cdd4cd1..0e6754a72888dee775826ff0d86e68c6678721b6 100644 --- a/Distributed/DistributeDatabaseDrawEts/README.md +++ b/Distributed/DistributeDatabaseDrawEts/README.md @@ -6,9 +6,13 @@ ![](figures/2.gif) + + # 2.相关概念 -[Path组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-drawing-components-path.md) +[Canvas组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-components-canvas-canvas.md) + +[CanvasRenderingContext2D对象](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-canvasrenderingcontext2d.md) [分布式数据库](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-distributed-data.md) @@ -18,65 +22,62 @@ 完成本篇Codelab我们首先要完成开发环境的搭建,本示例以**Hi3516DV300**开发板为例,参照以下步骤进行: -1. [获取OpenHarmony系统版本](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/get-code/sourcecode-acquire.md#%E8%8E%B7%E5%8F%96%E6%96%B9%E5%BC%8F3%E4%BB%8E%E9%95%9C%E5%83%8F%E7%AB%99%E7%82%B9%E8%8E%B7%E5%8F%96):标准系统解决方案(二进制) - - 以3.0版本为例: - - ![](figures/取版本.png) +1. [获取OpenHarmony系统版本](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/get-code/sourcecode-acquire.md#%E8%8E%B7%E5%8F%96%E6%96%B9%E5%BC%8F3%E4%BB%8E%E9%95%9C%E5%83%8F%E7%AB%99%E7%82%B9%E8%8E%B7%E5%8F%96):标准系统解决方案(二进制)。 -2. 搭建烧录环境 + 以3.0版本为例: - 1. [完成DevEco Device Tool的安装](https://device.harmonyos.com/cn/docs/documentation/guide/install_windows-0000001050164976) + ![](figures/取版本.png) - 2. [完成Hi3516开发板的烧录](https://device.harmonyos.com/cn/docs/documentation/guide/hi3516_upload-0000001052148681) - -3. 搭建开发环境 +2. 搭建烧录环境。 + 1. [完成DevEco Device Tool的安装](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-standard-env-setup.md) + 2. [完成Hi3516开发板的烧录](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-lite-steps-hi3516-burn.md) +3. 1. 开始前请参考[工具准备](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/start-overview.md#%E5%B7%A5%E5%85%B7%E5%87%86%E5%A4%87),完成DevEco Studio的安装和开发环境配置。 2. 开发环境配置完成后,请参考[使用工程向导](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/start-with-ets.md#%E5%88%9B%E5%BB%BAets%E5%B7%A5%E7%A8%8B)创建工程(模板选择“Empty Ability”),选择JS或者eTS语言开发。 3. 工程创建完成后,选择使用[真机进行调测](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/start-with-ets.md#%E4%BD%BF%E7%94%A8%E7%9C%9F%E6%9C%BA%E8%BF%90%E8%A1%8C%E5%BA%94%E7%94%A8)。 # 4.分布式组网 -本章节以系统自带的音乐播放器为例,介绍如何完成两台设备的分布式组网。 +本章节以系统自带的音乐播放器为例(具体以实际的应用为准),介绍如何完成两台设备的分布式组网。 -1. 硬件准备:准备两台烧录相同的版本系统的**Hi3516DV300**开发板A,B。 +1. 硬件准备:准备两台烧录相同的版本系统的**Hi3516DV300**开发板A、B。 -2. 两个开发板A,B配置在同一个WiFi网络之下。 +2. 开发板A、B连接同一个WiFi网络。 - 打开设置--\>WLAN--\>点击右侧WiFi开关--\>点击目标WiFi并输入密码。 + 打开设置--\>WLAN--\>点击右侧WiFi开关--\>点击目标WiFi并输入密码。 - ![](figures/IMG_20211217_144057.jpg) + ![](figures/IMG_20211217_144057.jpg) -3. 将设备A,B设置为互相信任的设备。 +3. 将设备A,B设置为互相信任的设备。 - 找到系统应用“音乐”。 - ![](figures/音乐.png) + ![](figures/音乐.png) - - 设备A打开音乐,点击左下角流转按钮,弹出列表框,在列表中会展示远端设备的id。 + - 设备A打开音乐,点击左下角流转按钮,弹出列表框,在列表中会展示远端设备的id。 - ![](figures/IMG_20211213_103011.jpg) + ![](figures/IMG_20211213_103011.jpg) - - 选择远端设备B的id,另一台开发板(设备B)会弹出验证的选项框。 + - 选择远端设备B的id,另一台开发板(设备B)会弹出验证的选项框。 - ![](figures/信.png) + ![](figures/信.png) - - 设备B点击允许,设备B将会弹出随机PIN码,将设备B的PIN码输入到设备A的PIN码填入框中。 + - 设备B点击允许,设备B将会弹出随机PIN码,将设备B的PIN码输入到设备A的PIN码填入框中。 - ![](figures/pin.png)![](figures/确认.png) + ![](figures/pin.png)![](figures/确认.png) - 配网完毕。 + 配网完毕。 # 5.代码结构解读 -本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在[参考](参考.md)中提供下载方式,整个工程的代码结构如下: +本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在[参考](zh-cn_topic_0000001193093116.md)中提供下载方式,整个工程的代码结构如下: ![](figures/zh-cn_image_0000001192614852.png) -- common:存放公共资源 +- common:存放公共资源 - media:存放图片 + media:存放图片 - model:存放数据模型类 @@ -84,243 +85,262 @@ - RemoteDeviceModel.ts:远程设备类 -- pages:存放页面 +- pages:存放页面 - index.ets:主页面 + index.ets:主页面 - config.json:配置文件 # 6.编写数据类对象 -1. 编写分布式数据类对象 - - 我们需要创建RemoteDeviceModel类来完成远程设备管理的初始化,RemoteDeviceModel .ts代码如下: - - ``` - import deviceManager from '@ohos.distributedHardware.deviceManager'; - var SUBSCRIBE_ID = 100; - export default class RemoteDeviceModel { - // 设备列表 - deviceList: any[] = [] - // 回调 - callback: any - // 设备管理Manager - #deviceManager: any - // 构造方法 - constructor() { - } - //注册设备回调方法 - registerDeviceListCallback(callback) { - if (typeof (this.#deviceManager) === 'undefined') { - let self = this; - deviceManager.createDeviceManager('com.ohos.distributedRemoteStartFA', (error, value) => { - if (error) { - console.error('createDeviceManager failed.'); - return; - } - self.#deviceManager = value; - self.registerDeviceListCallback_(callback); - }); - } else { - this.registerDeviceListCallback_(callback); - } - } - //注册设备回调方法 - registerDeviceListCallback_(callback) { - this.callback = callback; - if (this.#deviceManager == undefined) { - this.callback(); - return; - } - - console.info('CookBook[RemoteDeviceModel] getTrustedDeviceListSync begin'); - var list = this.#deviceManager.getTrustedDeviceListSync(); - if (typeof (list) != 'undefined' && typeof (list.length) != 'undefined') { - this.deviceList = list; - } - this.callback(); - let self = this; - this.#deviceManager.on('deviceStateChange', (data) => { - switch (data.action) { - case 0: - self.deviceList[self.deviceList.length] = data.device; - self.callback(); - if (self.authCallback != null) { - self.authCallback(); - self.authCallback = null; - } - break; - case 2: - if (self.deviceList.length > 0) { - for (var i = 0; i < self.deviceList.length; i++) { - if (self.deviceList[i].deviceId === data.device.deviceId) { - self.deviceList[i] = data.device; - break; - } - } - } - self.callback(); - break; - case 1: - if (self.deviceList.length > 0) { - var list = []; - for (var i = 0; i < self.deviceList.length; i++) { - if (self.deviceList[i].deviceId != data.device.deviceId) { - list[i] = data.device; - } - } - self.deviceList = list; - } - self.callback(); - break; - default: - break; - } - }); - this.#deviceManager.on('deviceFound', (data) => { - console.info('CookBook[RemoteDeviceModel] deviceFound data=' + JSON.stringify(data)); - console.info('CookBook[RemoteDeviceModel] deviceFound self.deviceList=' + self.deviceList); - console.info('CookBook[RemoteDeviceModel] deviceFound self.deviceList.length=' + self.deviceList.length); - for (var i = 0; i < self.discoverList.length; i++) { - if (self.discoverList[i].deviceId === data.device.deviceId) { - console.info('CookBook[RemoteDeviceModel] device founded, ignored'); - return; - } - } - self.discoverList[self.discoverList.length] = data.device; - self.callback(); - }); - this.#deviceManager.on('discoverFail', (data) => { - console.info('CookBook[RemoteDeviceModel] discoverFail data=' + JSON.stringify(data)); - }); - this.#deviceManager.on('serviceDie', () => { - console.error('CookBook[RemoteDeviceModel] serviceDie'); - }); - - SUBSCRIBE_ID = Math.floor(65536 * Math.random()); - var info = { - subscribeId: SUBSCRIBE_ID, - mode: 0xAA, - medium: 2, - freq: 2, - isSameAccount: false, - isWakeRemote: true, - capability: 0 - }; - console.info('CookBook[RemoteDeviceModel] startDeviceDiscovery ' + SUBSCRIBE_ID); - this.#deviceManager.startDeviceDiscovery(info); - } - //身份验证 - authDevice(deviceId, callback) { - console.info('CookBook[RemoteDeviceModel] authDevice ' + deviceId); - for (var i = 0; i < this.discoverList.length; i++) { - if (this.discoverList[i].deviceId === deviceId) { - console.info('CookBook[RemoteDeviceModel] device founded, ignored'); - let extraInfo = { - "targetPkgName": 'com.ohos.distributedRemoteStartFA', - "appName": 'demo', - "appDescription": 'demo application', - "business": '0' - }; - let authParam = { - "authType": 1, - "appIcon": '', - "appThumbnail": '', - "extraInfo": extraInfo - }; - console.info('CookBook[RemoteDeviceModel] authenticateDevice ' + JSON.stringify(this.discoverList[i])); - let self = this; - this.#deviceManager.authenticateDevice(this.discoverList[i], authParam, (err, data) => { - if (err) { - console.info('CookBook[RemoteDeviceModel] authenticateDevice failed, err=' + JSON.stringify(err)); - self.authCallback = null; - } else { - console.info('CookBook[RemoteDeviceModel] authenticateDevice succeed, data=' + JSON.stringify(data)); - self.authCallback = callback; - } - }); - } - } - } - //取消注册设备回调方法 - unregisterDeviceListCallback() { - console.info('CookBook[RemoteDeviceModel] stopDeviceDiscovery ' + SUBSCRIBE_ID); - this.#deviceManager.stopDeviceDiscovery(SUBSCRIBE_ID); - this.#deviceManager.off('deviceStateChange'); - this.#deviceManager.off('deviceFound'); - this.#deviceManager.off('discoverFail'); - this.#deviceManager.off('serviceDie'); - this.deviceList = []; - } - } - ``` - -2. 编写远程设备类对象 - - 我们需要创建KvStoreModel类来完成[分布式数据管理](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-distributed-data.md)的初始化工作。首先调用distributedData.createKVManager接口创建一个KVManager对象实例,用于管理数据库对象。然后调用KVManager.getKVStore接口创建并获取KVStore数据库。最后对外提供put、setDataChangeListener方法用于数据写入和订阅数据更新通知。KvStoreModel.ts代码如下: - - ``` - import distributedData from '@ohos.data.distributeddata'; - const STORE_ID = 'DrawBoard_kvstore'; - - export default class KvStoreModel { - kvManager: any; - kvStore: any; - constructor() { - } - // 创建createKvStore对象实例 - createKvStore(callback: any) { - if (typeof (this.kvStore) === 'undefined') { - var config = { - bundleName: 'com.huawei.cookbook', - userInfo: { - userId: '0', - userType: 0 - } - }; - let self = this; - console.info('DrawBoard[KvStoreModel] createKVManager begin'); - distributedData.createKVManager(config).then((manager) => { - console.info('DrawBoard[KvStoreModel] createKVManager success, kvManager=' + JSON.stringify(manager)); - self.kvManager = manager; - var options = { - createIfMissing: true, - encrypt: false, - backup: false, - autoSync: true, - kvStoreType: 1, - schema: '', - securityLevel: 3, - }; - console.info('DrawBoard[KvStoreModel] kvManager.getKVStore begin'); - self.kvManager.getKVStore(STORE_ID, options).then((store: any) => { - console.info('DrawBoard[KvStoreModel] getKVStore success, kvStore=' + store); - self.kvStore = store; - callback(); - }); - console.info('DrawBoard[KvStoreModel] kvManager.getKVStore end'); - }); - console.info('DrawBoard[KvStoreModel] createKVManager end'); - } else { - callback(); - } - } - // 添加数据 - put(key: any, value: any) { - if (typeof (this.kvStore) === 'undefined') { - return; - } - console.info('DrawBoard[KvStoreModel] kvStore.put ' + key + '=' + value); - this.kvStore.put(key, value).then((data: any) => { - this.kvStore.get(key).then((data:any) => { - console.info('DrawBoard[KvStoreModel] kvStore.get ' + key + '=' + JSON.stringify(data)); - }); - console.info('DrawBoard[KvStoreModel] kvStore.put ' + key + ' finished, data=' + JSON.stringify(data)); - }).catch((err: JSON) => { - console.error('DrawBoard[KvStoreModel] kvStore.put ' + key + ' failed, ' + JSON.stringify(err)); - }); - } - // 获取数据 +1. 编写分布式数据类对象 + + 我们需要创建RemoteDeviceModel类来完成远程设备管理的初始化,RemoteDeviceModel .ts代码如下: + + ``` + import deviceManager from '@ohos.distributedHardware.deviceManager'; + var SUBSCRIBE_ID = 100; + export default class RemoteDeviceModel { + // 设备列表 + deviceList: any[] = [] + // 回调 + callback: any + // 设备管理Manager + #deviceManager: any + // 构造方法 + constructor() { + } + //注册设备回调方法 + registerDeviceListCallback(callback) { + if (typeof (this.#deviceManager) === 'undefined') { + let self = this; + deviceManager.createDeviceManager('com.ohos.distributedRemoteStartFA', (error, value) => { + if (error) { + console.error('createDeviceManager failed.'); + return; + } + self.#deviceManager = value; + self.registerDeviceListCallback_(callback); + }); + } else { + this.registerDeviceListCallback_(callback); + } + } + //注册设备回调方法 + registerDeviceListCallback_(callback) { + this.callback = callback; + if (this.#deviceManager == undefined) { + this.callback(); + return; + } + + console.info('CookBook[RemoteDeviceModel] getTrustedDeviceListSync begin'); + var list = this.#deviceManager.getTrustedDeviceListSync(); + if (typeof (list) != 'undefined' && typeof (list.length) != 'undefined') { + this.deviceList = list; + } + this.callback(); + let self = this; + this.#deviceManager.on('deviceStateChange', (data) => { + switch (data.action) { + case 0: + self.deviceList[self.deviceList.length] = data.device; + self.callback(); + if (self.authCallback != null) { + self.authCallback(); + self.authCallback = null; + } + break; + case 2: + if (self.deviceList.length > 0) { + for (var i = 0; i < self.deviceList.length; i++) { + if (self.deviceList[i].deviceId === data.device.deviceId) { + self.deviceList[i] = data.device; + break; + } + } + } + self.callback(); + break; + case 1: + if (self.deviceList.length > 0) { + var list = []; + for (var i = 0; i < self.deviceList.length; i++) { + if (self.deviceList[i].deviceId != data.device.deviceId) { + list[i] = data.device; + } + } + self.deviceList = list; + } + self.callback(); + break; + default: + break; + } + }); + this.#deviceManager.on('deviceFound', (data) => { + console.info('CookBook[RemoteDeviceModel] deviceFound data=' + JSON.stringify(data)); + console.info('CookBook[RemoteDeviceModel] deviceFound self.deviceList=' + self.deviceList); + console.info('CookBook[RemoteDeviceModel] deviceFound self.deviceList.length=' + self.deviceList.length); + for (var i = 0; i < self.discoverList.length; i++) { + if (self.discoverList[i].deviceId === data.device.deviceId) { + console.info('CookBook[RemoteDeviceModel] device founded, ignored'); + return; + } + } + self.discoverList[self.discoverList.length] = data.device; + self.callback(); + }); + this.#deviceManager.on('discoverFail', (data) => { + console.info('CookBook[RemoteDeviceModel] discoverFail data=' + JSON.stringify(data)); + }); + this.#deviceManager.on('serviceDie', () => { + console.error('CookBook[RemoteDeviceModel] serviceDie'); + }); + + SUBSCRIBE_ID = Math.floor(65536 * Math.random()); + var info = { + subscribeId: SUBSCRIBE_ID, + mode: 0xAA, + medium: 2, + freq: 2, + isSameAccount: false, + isWakeRemote: true, + capability: 0 + }; + console.info('CookBook[RemoteDeviceModel] startDeviceDiscovery ' + SUBSCRIBE_ID); + this.#deviceManager.startDeviceDiscovery(info); + } + //身份验证 + authDevice(deviceId, callback) { + console.info('CookBook[RemoteDeviceModel] authDevice ' + deviceId); + for (var i = 0; i < this.discoverList.length; i++) { + if (this.discoverList[i].deviceId === deviceId) { + console.info('CookBook[RemoteDeviceModel] device founded, ignored'); + let extraInfo = { + "targetPkgName": 'com.ohos.distributedRemoteStartFA', + "appName": 'demo', + "appDescription": 'demo application', + "business": '0' + }; + let authParam = { + "authType": 1, + "appIcon": '', + "appThumbnail": '', + "extraInfo": extraInfo + }; + console.info('CookBook[RemoteDeviceModel] authenticateDevice ' + JSON.stringify(this.discoverList[i])); + let self = this; + this.#deviceManager.authenticateDevice(this.discoverList[i], authParam, (err, data) => { + if (err) { + console.info('CookBook[RemoteDeviceModel] authenticateDevice failed, err=' + JSON.stringify(err)); + self.authCallback = null; + } else { + console.info('CookBook[RemoteDeviceModel] authenticateDevice succeed, data=' + JSON.stringify(data)); + self.authCallback = callback; + } + }); + } + } + } + //取消注册设备回调方法 + unregisterDeviceListCallback() { + console.info('CookBook[RemoteDeviceModel] stopDeviceDiscovery ' + SUBSCRIBE_ID); + this.#deviceManager.stopDeviceDiscovery(SUBSCRIBE_ID); + this.#deviceManager.off('deviceStateChange'); + this.#deviceManager.off('deviceFound'); + this.#deviceManager.off('discoverFail'); + this.#deviceManager.off('serviceDie'); + this.deviceList = []; + } + } + ``` + +2. 编写远程设备类对象 + + 我们需要创建KvStoreModel类来完成[分布式数据管理](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-distributed-data.md)的初始化工作。首先调用distributedData.createKVManager接口创建一个KVManager对象实例,用于管理数据库对象。然后调用KVManager.getKVStore接口创建并获取KVStore数据库。最后对外提供put、setDataChangeListener方法用于数据写入和订阅数据更新通知。KvStoreModel.ts代码如下: + + ``` + import distributedData from '@ohos.data.distributedData'; + + const STORE_ID = 'DrawBoard_kvstore'; + + export default class KvStoreModel { + kvManager: any; + kvStore: any; + constructor() { + } + createKvStore(callback: any) { + if (typeof (this.kvStore) === 'undefined') { + var config = { + bundleName: 'com.huawei.cookbook', + userInfo: { + userId: '0', + userType: 0 + } + }; + let self = this; + console.info('DrawBoard[KvStoreModel] createKVManager begin'); + distributedData.createKVManager(config).then((manager) => { + console.info('DrawBoard[KvStoreModel] createKVManager success, kvManager=' + JSON.stringify(manager)); + self.kvManager = manager; + let options = { + createIfMissing: true, + encrypt: false, + backup: false, + autoSync: true, + kvStoreType: 0, + schema: '', + securityLevel: 1, + }; + console.info('DrawBoard[KvStoreModel] kvManager.getKVStore begin'); + self.kvManager.getKVStore(STORE_ID, options).then((store) => { + console.info('DrawBoard[KvStoreModel] getKVStore success, kvStore=' + store); + self.kvStore = store; + try { + self.kvStore.enableSync(true).then((err) => { + console.log('enableSync success'); + }).catch((err) => { + console.log('enableSync fail ' + JSON.stringify(err)); + }); + }catch(e) { + console.log('EnableSync e ' + e); + } + callback(); + }); + console.info('DrawBoard[KvStoreModel] kvManager.getKVStore end'); + }); + console.info('DrawBoard[KvStoreModel] createKVManager end'); + } else { + console.info('DrawBoard[KvStoreModel] KVManager is exist'); + callback(); + } + } + + broadcastMessage(msg: any) { + console.info('DrawBoard[KvStoreModel] broadcastMessage ' + msg); + var num = Math.random(); + let self = this; + this.createKvStore(() => { + self.put(msg, num); + }); + } + + put(key: any, value: any) { + if (typeof (this.kvStore) === 'undefined') { + return; + } + console.info('DrawBoard[KvStoreModel] kvStore.put ' + key + '=' + value); + this.kvStore.put(key, value).then((data: any) => { + this.kvStore.get(key).then((data:any) => { + console.info('DrawBoard[KvStoreModel] kvStore.get ' + key + '=' + JSON.stringify(data)); + }); + console.info('DrawBoard[KvStoreModel] kvStore.put ' + key + ' finished, data=' + JSON.stringify(data)); + }).catch((err: JSON) => { + console.error('DrawBoard[KvStoreModel] kvStore.put ' + key + ' failed, ' + JSON.stringify(err)); + }); + } + get(key: any,callback: any) { this.createKvStore(() => { this.kvStore.get(key, function (err: any ,data: any) { @@ -329,212 +349,254 @@ }); }) } - // 监听数据变化 - setDataChangeListener(callback: any) { - let self = this; - this.createKvStore(() => { - self.kvStore.on('dataChange', 1, (data: any) => { - if (data.updateEntries.length > 0) { - callback(data); - } - }); - }); - } - } - ``` + + setOnMessageReceivedListener(callback: any) { + console.info('DrawBoard[KvStoreModel] setOnMessageReceivedListener '); + let self = this; + this.createKvStore(() => { + console.info('DrawBoard[KvStoreModel] kvStore.on(dataChange) begin'); + self.kvStore.on('dataChange', 2, (data: any) => { + console.info('DrawBoard[KvStoreModel] dataChange, ' + JSON.stringify(data)); + console.info('DrawBoard[KvStoreModel] dataChange, insert ' + data.insertEntries.length + ' udpate ' + + data.updateEntries.length); + if (data.insertEntries.length < 1 && data.updateEntries.length < 1) { + return; + } + + callback(data); + }); + console.info('DrawBoard[KvStoreModel] kvStore.on(dataChange) end'); + }); + } + setDataChangeListener(callback) { + console.info('DrawBoard[KvStoreModel] setDataChangeListener come in'); + let self = this; + this.createKvStore(() => { + console.info('DrawBoard[KvStoreModel] setDataChangeListener createKvStore'); + self.kvStore.on('dataChange',2, (data: any) => { + console.info('DrawBoard[KvStoreModel] setDataChangeListener kvStore.on'); + if (data.updateEntries.length > 0) { + console.info('DrawBoard[KvStoreModel] setDataChangeListener callback'); + callback(data); + } + }); + }); + } + } + ``` # 7.页面设计 -分布式手写板页面主要由全屏Path绘制区、顶部操作栏组成。为了实现弹框选择设备的效果,在最外层添加了自定义弹框组件。Path组件设置为全屏显示,根据手指触摸的屏幕坐标直接通过Path绘制轨迹;顶部操作栏加入撤回图标、设备选择图标;自定义弹框加入标题、设备列表。页面样式请在[参考](参考.md)中查看,页面布局在index.ets中实现。 +分布式手写板页面主要由全屏Path绘制区、顶部操作栏组成。为了实现弹框选择设备的效果,在最外层添加了自定义弹框组件。Path组件设置为全屏显示,根据手指触摸的屏幕坐标直接通过Path绘制轨迹;顶部操作栏加入撤回图标、设备选择图标;自定义弹框加入标题、设备列表。页面样式请在[参考](zh-cn_topic_0000001193093116.md)中查看,页面布局在index.ets中实现。 在index.ets中按照如下步骤编写: -1. 页面整体布局 - - ``` - @Entry - @Component - struct Index { - build() { - Column({ space: 1 }) { - // 用于标题栏布局 - Flex() { - }.backgroundColor(Color.Grey).width('100%').height('10%') - // 用于Path绘制区布局 - Flex() { - }.width('100%').height('90%') - }.height('100%').width('100%') - } - ``` - -2. 标题栏布局 - - ``` - @Entry - @Component - struct Index { - build() { - Column({ space: 1 }) { - Flex() { - Image($r('app.media.goback')).width(70).height(70).position({ x: 30, y: 0 }) - Image($r('app.media.ic_hop')).width(70).height(70) - .align(Alignment.TopEnd) - .flexGrow(1) - .position({ x: 375, y: 0 }) - }.backgroundColor(Color.Grey).width('100%').height('10%') - ... - }.height('100%').width('100%') - } - ``` - -3. Path绘制区布局 - - ``` - @Entry - @Component - struct Index { - build() { - Column({ space: 1 }) { - Flex() { - ... - }.backgroundColor(Color.Grey).width('100%').height('10%') - Flex() { - Path().commands(this.pathCommands).strokeWidth(4).fill('none').stroke(Color.Black) - .width('100%') - .height('100%') - }.width('100%').height('90%') - }.height('100%').width('100%') - } - ``` - -4. 自定义弹框设计并引入到主页面中 - - 1. 自定义弹框设计 - - ``` - @CustomDialog - struct CustomDialogExample { - controller: CustomDialogController - cancel: () => void - confirm: (deviceId, deviceName) => void - startAbility: (deviceId, deviceName, positionList) => void - deviceList:() => void - positionList:() => void - build() { - Column() { - Text('设备列表').width('70%').fontSize(20).margin({ top: 10, bottom: 10 }) - Flex({ justifyContent: FlexAlign.SpaceAround }) { - List({ space: 20, initialIndex: 0 }) { - ForEach(this.deviceList, (item) => { - ListItem() { - Text('' + item.name) - .width('100%').height(100).fontSize(16) - .textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF) - .onClick((event: ClickEvent) =>{ - this.controller.close(); - this.startAbility(item.id, item.name, this.positionList) - }) - }.editable(true) - }, item => item.id) - } - .listDirection(Axis.Vertical) // 排列方向 - .divider({ strokeWidth: 2, color: 0xFFFFFF, startMargin: 20, endMargin: 20 }) // 每行之间的分界线 - .edgeEffect(EdgeEffect.None) // 滑动到边缘无效果 - .chainAnimation(false) // 联动特效关闭 - .onScrollIndex((firstIndex: number, lastIndex: number) => { - console.info('first' + firstIndex) - console.info('last' + lastIndex) - }) - }.margin({ bottom: 10 }) - } - } +1. 页面整体布局 + + ``` + @Entry + @Component + struct Index { + build() { + Column({ space: 1 }) { + // 用于标题栏布局 + Row() { + }.backgroundColor(Color.Grey).width('100%').height('10%') + // 用于Path绘制区布局 + Row() { + }.width('100%').height('90%') + }.height('100%').width('100%') } - ``` + ``` + +2. 标题栏布局 + + ``` + @Entry + @Component + struct Index { + build() { + Column({ space: 1 }) { + Row() { + Image($r('app.media.goback')).width(100).height(100).margin({left:10}) + Blank() + Image($r('app.media.ic_hop')).width(100).height(100).margin({right:10}) + }.backgroundColor(Color.Grey).width('100%').height('10%') + ... + }.height('100%').width('100%') + } + } + ``` + +3. Canvas绘制区布局 + + ``` + @Entry + @Component + struct Index { + build() { + Column({ space: 1 }) { + Row() { + ... + }.backgroundColor(Color.Grey).width('100%').height('10%') + Row() { + Canvas(this.context) + .width('100%') + .height('100%') + .backgroundColor('#FFFFFF') + }.width('100%').height('90%') + }.height('100%').width('100%') + } + ``` + +4. 自定义弹框设计并引入到主页面中 + 1. 自定义弹框设计 + + ``` + @CustomDialog + struct CustomDialogExample { + controller: CustomDialogController + cancel: () => void + confirm: (deviceId, deviceName) => void + startAbility: (deviceId, deviceName, positionList) => void + deviceList:() => void + positionList:() => void + private selectedIndex: number = 0 + build() { + Column() { + Text('选择设备') + .fontSize(20) + .width('100%') + .textAlign(TextAlign.Center) + .fontColor(Color.Black) + .fontWeight(FontWeight.Bold) + List() { + ForEach(this.deviceList, (item, index) => { + ListItem() { + Row() { + Text(item.name) + .fontSize(20) + .width('90%') + .fontColor(Color.Black) + if (this.deviceList.indexOf(item) == this.selectedIndex) { + Image($r('app.media.checked')) + .width('8%') + .objectFit(ImageFit.Contain) + } else { + Image($r('app.media.uncheck')) + .width('8%') + .objectFit(ImageFit.Contain) + } + } + .height(55) + .onClick(() =>{ + this.selectedIndex = index + this.controller.close(); + this.startAbility(item.id, item.name, this.positionList) + }) + } + }, item => item.id) + } + + Button() { + Text('取消') + .fontColor('#0D9FFB') + .width('90%') + .textAlign(TextAlign.Center) + .fontSize(20) + } + .type(ButtonType.Capsule) + .backgroundColor(Color.White) + .onClick(() => { + this.controller.close() + }) + } + .backgroundColor(Color.White) + .border({ color: Color.White, radius: 20 }) + .padding(10) + } + } + ``` - 2. 引入到主页面 + 2. 引入到主页面 - ``` - @Entry - @Component - struct Index { - ... - dialogController: CustomDialogController = new CustomDialogController({ - builder: CustomDialogExample({ cancel: this.onCancel, confirm: this.onAccept, deviceList: this.deviceList,positionList: this.positionList,startAbility: this.startAbilityContinuation }), - cancel: this.existApp, - autoCancel: true, - deviceList: this.deviceList, - positionList: this.positionList - }) - onCancel() { - console.info('Callback when the first button is clicked') - } - onAccept() { - console.info('Click when confirm') - } - existApp() { - console.info('Click the callback in the blank area') - } - build() { + ``` + @Entry + @Component + struct Index { ... - } - } - ``` - -5. 为页面设置原始数据 - - 1. 在index.ets文件的顶部设置常量数据 - - ``` - // 默认设备 - var DEVICE_LIST_LOCALHOST = { name: '本机', id: 'localhost' }; - // 用于存放点位信息的key值常量 - const CHANGE_POSITION = 'change_position'; - // path组件Command参数初始化值 - const DEfAULT_PATH_COMMAND = ''; - ``` - - 2. 在Index组件(Component)中设置基本参数 - - ``` - @Entry - @Component - struct Index { - // 触控起始位置X轴坐标 - @State startX: number = 0 - // 触控起始位置Y轴坐标 - @State startY: number = 0 - // 触控移动后的位置X轴坐标 - @State moveX: number = 0 - // 触控移动后的位置Y轴坐标 - @State moveY: number = 0 - // 触控结束后的位置X轴坐标 - @State endX: number = 0 - // 触控结束后的位置Y轴坐标 - @State endY: number = 0 - // Path 组件Command属性值,默认为'' - @State pathCommands: string = DEfAULT_PATH_COMMAND - // 设备列表 - @State deviceList: any[] = [] - // BUNDLE_NAME - private BUNDLE_NAME: string = "com.huawei.cookbook"; - // 分布式数据库类对象 - private kvStoreModel: KvStoreModel = new KvStoreModel() - // 远程设备类对象 - private remoteDeviceModel: RemoteDeviceModel = new RemoteDeviceModel() - // 点位集合 - @State positionList: any[] = [] - // 初始化数据 - @State initialData: any[] = [] - // 是否同步 - private isNeedSync: boolean = false - // 间隔ID - private intervalID: number = 0 - ... - build() { - ... - } - } - ``` + dialogController: CustomDialogController = new CustomDialogController({ + builder: CustomDialogExample({ cancel: this.onCancel, confirm: this.onAccept, deviceList: this.deviceList,positionList: this.positionList,startAbility: this.startAbilityContinuation }), + cancel: this.existApp, + autoCancel: true, + deviceList: this.deviceList, + positionList: this.positionList + }) + onCancel() { + console.info('Callback when the first button is clicked') + } + onAccept() { + console.info('Click when confirm') + } + existApp() { + console.info('Click the callback in the blank area') + } + build() { + ... + } + } + ``` + +5. 为页面设置原始数据 + 1. 在index.ets文件的顶部设置常量数据 + + ``` + // 默认设备 + var DEVICE_LIST_LOCALHOST = { name: '本机', id: 'localhost' }; + // 用于存放点位信息的key值常量 + const CHANGE_POSITION = 'change_position'; + ``` + + 2. 在Index组件(Component)中设置基本参数 + + ``` + @Entry + @Component + struct Index { + // 触控起始位置X轴坐标 + @State startX: number = 0 + // 触控起始位置Y轴坐标 + @State startY: number = 0 + // 触控移动后的位置X轴坐标 + @State moveX: number = 0 + // 触控移动后的位置Y轴坐标 + @State moveY: number = 0 + // 触控结束后的位置X轴坐标 + @State endX: number = 0 + // 触控结束后的位置Y轴坐标 + @State endY: number = 0 + // 设备列表 + @State deviceList: any[] = [] + // BUNDLE_NAME + private BUNDLE_NAME: string = "com.huawei.cookbook"; + // 分布式数据库类对象 + private kvStoreModel: KvStoreModel = new KvStoreModel() + // 远程设备类对象 + private remoteDeviceModel: RemoteDeviceModel = new RemoteDeviceModel() + // 点位集合 + @State positionList: any[] = [] + // 初始化数据 + @State initialData: any[] = [] + // 画布组件 + private settings: RenderingContextSettings = new RenderingContextSettings(true) + // CanvasRenderingContext2D对象 + private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings) + ... + build() { + ... + } + } + ``` # 8.设备拉起 @@ -542,135 +604,168 @@ ![](figures/1-0.gif) -1. 分享图标添加点击事件 - - ``` - Image($r('app.media.ic_hop')).width(70).height(70) - .align(Alignment.TopEnd) - .flexGrow(1) - .position({ x: 375, y: 0 }) - .onClick((event: ClickEvent) =>{ - this.onContinueAbilityClick() - }) - ``` - -2. 分布式拉起方法实现 - - ``` - onContinueAbilityClick() { - console.info('DrawBoard[IndexPage] onContinueAbilityClick'); - let self = this; - this.remoteDeviceModel.registerDeviceListCallback(() => { - console.info('DrawBoard[IndexPage] registerDeviceListCallback, callback entered'); - var list = []; - list[0] = DEVICE_LIST_LOCALHOST - var deviceList = self.remoteDeviceModel.deviceList; - console.info('DrawBoard[IndexPage] on remote device updated, count=' + deviceList.length); - for (var i = 0; i < deviceList.length; i++) { - console.info('DrawBoard[IndexPage] device ' + i + '/' + deviceList.length + ' deviceId=' - + deviceList[i].deviceId + ' deviceName=' + deviceList[i].deviceName + ' deviceType=' - + deviceList[i].deviceType); - list[i + 1] = { - name: deviceList[i].deviceName, - id: deviceList[i].deviceId, - }; - } - self.deviceList = list; - self.dialogController.open() - }); - } - ``` +1. config.json中添加分布式权限 + + ``` + { + ... + "module": { + ... + "reqPermissions": [{ + "name": "ohos.permission.DISTRIBUTED_DATASYNC" + }] + } + } + ``` + +2. 初始化方法中添加动态权限申请代码 + + ``` + async aboutToAppear() { + this.grantPermission() + ... + } + grantPermission() { + console.log('MusicPlayer[IndexPage] grantPermission') + let context = featureAbility.getContext() + context.requestPermissionsFromUser(['ohos.permission.DISTRIBUTED_DATASYNC'], 666, function (result) { + console.log(`MusicPlayer[IndexPage] grantPermission,requestPermissionsFromUser,result.requestCode=${result.requestCode}`) + }) + } + + ``` + +3. 分享图标添加点击事件 + + ``` + Image($r('app.media.ic_hop')).width(100).height(100).margin({right:10}) + .onClick(() =>{ + this.onContinueAbilityClick() + }) + ``` + +4. 分布式拉起方法实现 + + ``` + onContinueAbilityClick() { + console.info('DrawBoard[IndexPage] onContinueAbilityClick'); + let self = this; + this.remoteDeviceModel.registerDeviceListCallback(() => { + console.info('DrawBoard[IndexPage] registerDeviceListCallback, callback entered'); + var list = []; + list[0] = DEVICE_LIST_LOCALHOST + var deviceList = self.remoteDeviceModel.deviceList; + console.info('DrawBoard[IndexPage] on remote device updated, count=' + deviceList.length); + for (var i = 0; i < deviceList.length; i++) { + console.info('DrawBoard[IndexPage] device ' + i + '/' + deviceList.length + ' deviceId=' + + deviceList[i].deviceId + ' deviceName=' + deviceList[i].deviceName + ' deviceType=' + + deviceList[i].deviceType); + list[i + 1] = { + name: deviceList[i].deviceName, + id: deviceList[i].deviceId, + }; + } + self.deviceList = list; + self.dialogController.open() + }); + } + ``` # 9.笔记绘制 Path组件所在的Flex容器组件添加点击事件,并实现记录触控点位信息,绘制轨迹的功能。 -1. 添加点击事件 - - ``` - Flex() { - Path().commands(this.pathCommands).strokeWidth(4).fill('none').stroke(Color.Black) - .width('100%') - .height('100%') - }.onTouch((event: TouchEvent) => { - this.onTouchEvent(event) - }).width('100%').height('90%') - ``` - -2. 现记录触控点位信息的方法 - - ``` - onTouchEvent(event: TouchEvent) { - let position = {}; - switch(event.type){ - // 触控按下 - case TouchType.Down: - this.startX = event.touches[0].x - this.startY = event.touches[0].y - this.pathCommands += ' M' + this.startX + ' ' + this.startY - position.isFirstPosition = true; - position.positionX = this.startX; - position.positionY = this.startY; - this.pushData(position); - break; - // 触控移动 - case TouchType.Move: - this.moveX = event.touches[0].x - this.moveY = event.touches[0].y - this.pathCommands += ' L' + this.moveX + ' ' + this.moveY - position.isFirstPosition = false; - position.positionX = this.moveX; - position.positionY = this.moveY; - this.pushData(position); - break; - // 触控抬起 - case TouchType.Up: - this.endX = event.touches[0].x - this.endY = event.touches[0].y - position.isFirstPosition = false; - position.positionX = this.moveX; - position.positionY = this.moveY; - this.pushData(position); - break; - default: - break - } - } - ``` +1. 添加点击事件 + + ``` + Row() { + Canvas(this.context) + .width('100%') + .height('100%') + .backgroundColor('#FFFFFF') + }.onTouch((event: TouchEvent) => { + this.onTouchEvent(event) + }).width('100%').height('90%') + ``` + +2. 现记录触控点位信息的方法 + + ``` + onTouchEvent(event: TouchEvent) { + let position = {}; + switch(event.type){ + case TouchType.Down: + this.startX = event.touches[0].x + this.startY = event.touches[0].y + position.isFirstPosition = true; + position.positionX = this.startX; + position.positionY = this.startY; + position.isEndPosition = false + this.context.beginPath() + this.context.lineWidth = 4 + this.context.lineJoin = 'miter' + this.context.moveTo(this.startX, this.startY) + this.pushData(position); + break; + case TouchType.Move: + this.moveX = event.touches[0].x + this.moveY = event.touches[0].y + position.isFirstPosition = false; + position.positionX = this.moveX; + position.positionY = this.moveY; + position.isEndPosition = false + this.context.lineTo(this.moveX, this.moveY) + this.pushData(position); + break; + case TouchType.Up: + this.endX = event.touches[0].x + this.endY = event.touches[0].y + position.isFirstPosition = false; + position.positionX = this.endX; + position.positionY = this.endY; + position.isEndPosition = true + this.context.stroke() + this.pushData(position); + break; + default: + break; + } + } + ``` # 10.笔记撤回 笔迹绘制时已经记录了所有笔迹上的坐标点,点击“撤回”按钮后,对记录的坐标点进行倒序删除,当删除最后一笔绘制的起始点坐标后停止删除,然后清空画布对剩余的坐标点进行重新绘制,至此撤回操作完成。此功能在index.ets中实现,代码如下: -1. 添加撤回事件 - - ``` - Image($r('app.media.goback')).width(70).height(70).position({ x: 30, y: 0 }) - .onClick((event: ClickEvent) =>{ - this.goBack() - }) - ``` - -2. 编写撤回事件 - - ``` - // 撤回上一笔绘制 - goBack() { - if (this.positionList.length > 0) { - for (let i = this.positionList.length - 1; i > -1; i--) { - if (this.positionList[i].isFirstPosition) { - this.positionList.pop(); - this.redraw(); - break; - } else { - this.positionList.pop(); - } - } - // 保存点位信息 - this.kvStoreModel.put(CHANGE_POSITION, JSON.stringify(this.positionList)); - } - } - ``` +1. 添加撤回事件 + + ``` + Image($r('app.media.goback')).width(100).height(100).margin({left:10}) + .onClick(() =>{ + this.goBack() + }) + ``` + +2. 编写撤回事件 + + ``` + // 撤回上一笔绘制 + goBack() { + if (this.positionList.length > 0) { + for (let i = this.positionList.length - 1; i > -1; i--) { + if (this.positionList[i].isFirstPosition) { + this.positionList.pop(); + this.redraw(); + break; + } else { + this.positionList.pop(); + } + } + // 保存点位信息 + this.kvStoreModel.put(CHANGE_POSITION, JSON.stringify(this.positionList)); + } + } + ``` # 11.轨迹同步 @@ -678,162 +773,171 @@ Path组件所在的Flex容器组件添加点击事件,并实现记录触控点 ![](figures/2-1.gif) -1. 设备拉起时,通过positionList将笔迹数据发送给目标设备,目标设备在aboutToAppear时将接收到的笔迹数据用Path绘制出来,从而实现笔迹的同步。此功能在index.ets中实现,关键代码如下: +1. 设备拉起时,通过positionList将笔迹数据发送给目标设备,目标设备在aboutToAppear时将接收到的笔迹数据用Canvas绘制出来,从而实现笔迹的同步。此功能在index.ets中实现,关键代码如下: - 发送端 + 发送端 - ``` - startAbilityContinuation(deviceId: string, deviceName: string,positionList: any[] ) { - // 参数 - var params = { - // 点位集合 - positionList: JSON.stringify(positionList) - } - // 拉起的设备、ability信息等 - var wantValue = { - bundleName: 'com.huawei.cookbook', - abilityName: 'com.huawei.distributedatabasedrawetsopenh.MainAbility', - deviceId: deviceId, - parameters: params - }; - // 分布式拉起 - featureAbility.startAbility({ - want: wantValue - }).then((data) => { - console.info('DrawBoard[IndexPage] featureAbility.startAbility finished, ' + JSON.stringify(data)); - }); - } - ``` - - 接收端 - - ``` - // 函数在创建自定义组件的新实例后,在执行其build函数之前执行 - async aboutToAppear() { - console.info('DrawBoard[IndexPage] aboutToAppear begin'); + ``` + startAbilityContinuation(deviceId: string, deviceName: string,positionList: any[] ) { + // 参数 + var params = { + // 点位集合 + positionList: JSON.stringify(positionList) + } + // 拉起的设备、ability信息等 + var wantValue = { + bundleName: 'com.huawei.cookbook', + abilityName: 'com.huawei.distributedatabasedrawetsopenh.MainAbility', + deviceId: deviceId, + parameters: params + }; + // 分布式拉起 + featureAbility.startAbility({ + want: wantValue + }).then((data) => { + console.info('DrawBoard[IndexPage] featureAbility.startAbility finished, ' + JSON.stringify(data)); + }); + } + ``` + + 接收端 + + ``` + //函数在创建自定义组件的新实例后,在执行其build函数之前执行 + async aboutToAppear() { + // 申请分布式权限 + this.grantPermission() + console.log('DrawBoard[IndexPage] aboutToAppear begin'); this.initialData = [] let self = this - // 获取接收的数据 + //获取初被拉起时候传来的点位信息 await featureAbility.getWant() .then((Want) => { self.positionList = JSON.parse(Want.parameters.positionList) + console.log('Operation successful. self.positionList: ' + JSON.stringify(self.positionList.length)); }).catch((error) => { - console.error('Operation failed. Cause: ' + JSON.stringify(error)); + console.error('Operation failed. Cause: ' + JSON.stringify(error)); + }) + console.log('DrawBoard[IndexPage] aboutToAppear positionList length=' + self.positionList.length); + if (self.positionList.length > 0) { + self.positionList.forEach((num) => { + self.initialData.push(num); + }); + console.log('DrawBoard[IndexPage] aboutToAppear initialData='+JSON.stringify(self.initialData)) + // 根军传来的点位信息初始化画布中的手写笔画路径 + self.initDraw(); + } + console.log('DrawBoard[IndexPage] setDataChangeListener out setDataChangeListener') + // 监听分布式数据库中数据变化,并根据数据变化重新绘制路径 + self.kvStoreModel.setDataChangeListener((data) => { + console.log('DrawBoard[IndexPage] setDataChangeListener come in') + self.positionList = []; + data.updateEntries.forEach((num) => { + const list = JSON.parse(num.value.value); + console.log('DrawBoard[IndexPage] setDataChangeListener list=' + JSON.stringify(list)) + if(list.length === 0) { + console.log('DrawBoard[IndexPage] setDataChangeListener list.length === 0') + } else{ + list.forEach((num) => { + self.positionList.push(num); + }) + console.log('DrawBoard[IndexPage] setDataChangeListener positionList=' + JSON.stringify(self.positionList)) + } + self.redraw(); + }); + }); + } + ... + // 初始化画板轨迹 + initDraw() { + this.initialData.forEach((point)=>{ + if(point.isFirstPosition) { + this.context.beginPath() + this.context.lineWidth = 4 + this.context.lineJoin = 'miter' + this.context.moveTo(point.positionX, point.positionY) + } else{ + this.context.lineTo(point.positionX, point.positionY) + if(point.isEndPosition) { + this.context.stroke() + console.log('DrawBoard[IndexPage] initDraw context.stroke') + } + } }) - //如果传来的点位集合不为空 - if (this.positionList.length > 0) { - this.positionList.forEach((num) => { - this.initialData.push(num); - }); - // 初始化绘制方法 - this.initDraw(); - } - // 监听分布式数据库中点位信息变化 - this.kvStoreModel.setDataChangeListener((data) => { - self.positionList = []; - data.updateEntries.forEach((num) => { - const list = JSON.parse(num.value.value); - console.info('DrawBoard[IndexPage] setDataChangeListener list=' + JSON.stringify(list)) - if(list.length === 0) { - self.pathCommands = DEfAULT_PATH_COMMAND - } else{ - // 如果不为空,则将数据库中的点位信息添加到页面中 - list.forEach((num) => { - self.positionList.push(num); - }) - } - setTimeout(function() { - // 重绘路径 - self.redraw(); - }, 10); - }); - }); - } - ... - // 初始化画板轨迹 - initDraw() { - const self = this; - self.pathCommands = DEfAULT_PATH_COMMAND - this.intervalID = setInterval(function() { - if (self.initialData[0].isFirstPosition) { - self.pathCommands += ' M' + self.initialData[0].positionX + ' ' + self.initialData[0].positionY - } else { - self.pathCommands += ' L' + self.initialData[0].positionX + ' ' + self.initialData[0].positionY - } - self.initialData.shift(); - if (self.initialData.length < 1) { - clearInterval(self.intervalID); - self.intervalID = 0; - } - }, 10); - } - ``` - -2. 笔迹绘制时,为了避免分布式数据库写入过于频繁,需要使用定时器setInterval,笔迹数据每100ms写入一次。其他设备通过订阅分布式数据更新通知来获取最新的笔迹数据,然后重新绘制笔迹,从而实现笔迹同步。此功能在index.js中实现,关键代码如下: - - 发送端 - - ``` - // 将绘制笔迹写入分布式数据库 - pushData(position) { - this.isNeedSync = true; - this.positionList.push(position); - const self = this; - - // 使用定时器每100ms写入一次 - if (this.intervalID === 0) { - this.intervalID = setInterval(function() { - if (self.isNeedSync) { - self.kvStoreModel.put(CHANGE_POSITION, JSON.stringify(self.positionList)); - self.isNeedSync = false; - } - }, DATA_PUT_DELAY); - } - }, - ``` - - 接收端 - - ``` - onInit() { - ... - // 订阅分布式数据更新通知 - this.kvStoreModel.setDataChangeListener((data) => { - data.updateEntries.forEach((num) => { - this.positionList = []; - const list = JSON.parse(num.value.value); - list.forEach((num) => { - this.positionList.push(num); - }) - const self = this; - setTimeout(function() { - self.redraw(); - }, DRAW_DELAY); - }); - }); - }, - // 重新绘制笔迹 - redraw() { - // 清空画布 - this.ctx.clearRect(0, 0, CLEAR_WIDTH, CLEAR_HEIGHT); - this.positionList.forEach((num) => { - if (num.isStartPosition) { - this.ctx.beginPath(); - this.ctx.moveTo(num.positionX, num.positionY); - } else { - this.ctx.lineTo(num.positionX, num.positionY); - this.ctx.stroke(); - } - }); - }, - ``` + } + ``` + +2. 笔迹绘制时,为了避免分布式数据库写入过于频繁,我们设置为每画完一笔,将点位数据提交到数据库。其他设备通过订阅分布式数据更新通知来获取最新的笔迹数据,然后重新绘制笔迹,从而实现笔迹同步。此功能在index.js中实现,关键代码如下: + + 发送端 + + ``` + // 将绘制笔迹写入分布式数据库 + pushData(position: any) { + this.positionList.push(position); + console.log('DrawBoard[IndexPage] pushData positionList 1 =' + JSON.stringify(this.positionList.length)); + // 如果为最后手指离开屏幕的点,则写入数据库 + if(position.isEndPosition){ + this.kvStoreModel.put(CHANGE_POSITION, JSON.stringify(this.positionList)); + console.log('DrawBoard[IndexPage] pushData positionList 2 =' + JSON.stringify(this.positionList.length)); + } + } + ``` + + 接收端: + + ``` + onInit() { + ... + // 订阅分布式数据更新通知 + this.kvStoreModel.setDataChangeListener((data) => { + console.log('DrawBoard[IndexPage] setDataChangeListener come in') + self.positionList = []; + data.updateEntries.forEach((num) => { + const list = JSON.parse(num.value.value); + console.log('DrawBoard[IndexPage] setDataChangeListener list=' + JSON.stringify(list)) + if(list.length === 0) { + console.log('DrawBoard[IndexPage] setDataChangeListener list.length === 0') + } else{ + list.forEach((num) => { + self.positionList.push(num); + }) + console.log('DrawBoard[IndexPage] setDataChangeListener positionList=' + JSON.stringify(self.positionList)) + } + self.redraw(); + }); + }); + }, + // 重新绘制笔迹 + redraw() { + console.log('DrawBoard[IndexPage] redraw positionList= ' + JSON.stringify(this.positionList)) + this.context.clearRect(0,0, this.context.width,this.context.height) + if (this.positionList.length > 0 ) { + this.positionList.forEach((num) => { + console.log('DrawBoard[IndexPage] redraw num=') + console.log('DrawBoard[IndexPage] redraw out isFirstPosition=' + num.isFirstPosition) + if (num.isFirstPosition) { + console.log('DrawBoard[IndexPage] redraw isFirstPosition=' + num.isFirstPosition) + this.context.beginPath() + this.context.lineWidth = 4 + this.context.lineJoin = 'miter' + this.context.moveTo(num.positionX, num.positionY) + console.log('DrawBoard[IndexPage] redraw context.moveTo' + num.positionX+','+ num.positionY) + } else { + this.context.lineTo(num.positionX, num.positionY) + console.log('DrawBoard[IndexPage] redraw context.lineTo' + num.positionX+','+ num.positionY) + if(num.isEndPosition) { + this.context.stroke() + console.log('DrawBoard[IndexPage] redraw context.stroke') + } + } + }); + } + } + ``` 3. 笔迹撤回时,直接将撤回后的笔迹数据写入分布式数据库,其他设备也是通过订阅分布式数据更新通知来获取最新的笔迹数据,最终实现笔迹同步,这里不再做讲解。 # 12.恭喜你 -通过本教程的学习,您已经学会使用基于TS扩展的声明式开发范式中的分布式数据管理和分布式拉起以及利用Path组件绘制图形。 - -# 13.参考 - -[gitee地址](https://gitee.com/openharmony/codelabs/tree/master/Distributed/DistributeDatabaseDrawEts) \ No newline at end of file +通过本教程的学习,您已经学会使用基于TS扩展的声明式开发范式中的分布式数据管理和分布式拉起以及利用Canvas组件绘制图形。 diff --git a/Distributed/DistributeDatabaseDrawEts/entry/src/main/ets/MainAbility/model/KvStoreModel.ts b/Distributed/DistributeDatabaseDrawEts/entry/src/main/ets/MainAbility/model/KvStoreModel.ts index ce8d65984b14b02a14546a9c37c240eb565fd725..4c407fc0dc651b2d0e89e9329dc3ad74a560fbdb 100644 --- a/Distributed/DistributeDatabaseDrawEts/entry/src/main/ets/MainAbility/model/KvStoreModel.ts +++ b/Distributed/DistributeDatabaseDrawEts/entry/src/main/ets/MainAbility/model/KvStoreModel.ts @@ -51,6 +51,15 @@ export default class KvStoreModel { self.kvManager.getKVStore(STORE_ID, options).then((store) => { console.info('DrawBoard[KvStoreModel] getKVStore success, kvStore=' + store); self.kvStore = store; + try { + self.kvStore.enableSync(true).then((err) => { + console.log('enableSync success'); + }).catch((err) => { + console.log('enableSync fail ' + JSON.stringify(err)); + }); + }catch(e) { + console.log('EnableSync e ' + e); + } callback(); }); console.info('DrawBoard[KvStoreModel] kvManager.getKVStore end'); @@ -100,7 +109,7 @@ export default class KvStoreModel { let self = this; this.createKvStore(() => { console.info('DrawBoard[KvStoreModel] kvStore.on(dataChange) begin'); - self.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL, (data: any) => { + self.kvStore.on('dataChange', 2, (data: any) => { console.info('DrawBoard[KvStoreModel] dataChange, ' + JSON.stringify(data)); console.info('DrawBoard[KvStoreModel] dataChange, insert ' + data.insertEntries.length + ' udpate ' + data.updateEntries.length); diff --git a/Distributed/DistributeDatabaseDrawEts/entry/src/main/ets/MainAbility/pages/index.ets b/Distributed/DistributeDatabaseDrawEts/entry/src/main/ets/MainAbility/pages/index.ets index 6464bb2dd9bdccdd394482abc457561fbb8abd8f..88d663e81749f2634f3adb0f7f3bd5bfe377d2e5 100644 --- a/Distributed/DistributeDatabaseDrawEts/entry/src/main/ets/MainAbility/pages/index.ets +++ b/Distributed/DistributeDatabaseDrawEts/entry/src/main/ets/MainAbility/pages/index.ets @@ -4,7 +4,6 @@ import KvStoreModel from '../model/KvStoreModel'; import RemoteDeviceModel from '../model/RemoteDeviceModel'; var DEVICE_LIST_LOCALHOST = { name: '本机', id: 'localhost' }; const CHANGE_POSITION = 'change_position'; -const DEfAULT_PATH_COMMAND = ''; @CustomDialog struct CustomDialogExample { @@ -73,21 +72,20 @@ struct CustomDialogExample { @Entry @Component struct Index { - @State startX: number = 0 - @State startY: number = 0 - @State moveX: number = 0 - @State moveY: number = 0 - @State endX: number = 0 - @State endY: number = 0 - @State pathCommands: string = DEfAULT_PATH_COMMAND + private startX: number = 0 + private startY: number = 0 + private moveX: number = 0 + private moveY: number = 0 + private endX: number = 0 + private endY: number = 0 @State deviceList: any[] = [] private BUNDLE_NAME: string = "com.huawei.cookbook"; private kvStoreModel: KvStoreModel = new KvStoreModel() private remoteDeviceModel: RemoteDeviceModel = new RemoteDeviceModel() - @State positionList: any[] = [] - @State initialData: any[] = [] - private isNeedSync: boolean = false - private intervalID: number = 0 + private positionList: any[] = [] + private initialData: any[] = [] + private settings: RenderingContextSettings = new RenderingContextSettings(true) + private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings) dialogController: CustomDialogController = new CustomDialogController({ builder: CustomDialogExample({ cancel: this.onCancel, confirm: this.onAccept, deviceList: this.deviceList,positionList: this.positionList,startAbility: this.startAbilityContinuation }), @@ -119,33 +117,35 @@ struct Index { this.onContinueAbilityClick() }) }.backgroundColor(Color.Grey).width('100%').height('10%') - Flex() { - Path().commands(this.pathCommands).strokeWidth(4).fill('none').stroke(Color.Black) + Row() { + Canvas(this.context) .width('100%') .height('100%') + .backgroundColor('#FFFFFF') }.onTouch((event: TouchEvent) => { this.onTouchEvent(event) }).width('100%').height('90%') }.height('100%').width('100%') } // 函数在创建自定义组件的新实例后,在执行其build函数之前执行 - aboutToAppear() { + async aboutToAppear() { this.grantPermission() console.log('DrawBoard[IndexPage] aboutToAppear begin'); this.initialData = [] let self = this - featureAbility.getWant() + await featureAbility.getWant() .then((Want) => { self.positionList = JSON.parse(Want.parameters.positionList) console.log('Operation successful. self.positionList: ' + JSON.stringify(self.positionList.length)); }).catch((error) => { - console.error('Operation failed. Cause: ' + JSON.stringify(error)); - }) - console.log('DrawBoard[IndexPage] aboutToAppear positionList length=' + this.positionList.length); + console.error('Operation failed. Cause: ' + JSON.stringify(error)); + }) + console.log('DrawBoard[IndexPage] aboutToAppear positionList length=' + self.positionList.length); if (self.positionList.length > 0) { self.positionList.forEach((num) => { self.initialData.push(num); }); + console.log('DrawBoard[IndexPage] aboutToAppear initialData='+JSON.stringify(self.initialData)) self.initDraw(); } console.log('DrawBoard[IndexPage] setDataChangeListener out setDataChangeListener') @@ -157,16 +157,13 @@ struct Index { console.log('DrawBoard[IndexPage] setDataChangeListener list=' + JSON.stringify(list)) if(list.length === 0) { console.log('DrawBoard[IndexPage] setDataChangeListener list.length === 0') - self.pathCommands = DEfAULT_PATH_COMMAND } else{ list.forEach((num) => { self.positionList.push(num); }) console.log('DrawBoard[IndexPage] setDataChangeListener positionList=' + JSON.stringify(self.positionList)) } - setTimeout(function() { - self.redraw(); - }, 10); + self.redraw(); }); }); } @@ -179,40 +176,43 @@ struct Index { } // 初始化画板轨迹 initDraw() { - let self = this; - self.pathCommands = '' - self.intervalID = setInterval(function() { - if (self.initialData[0].isFirstPosition) { - self.pathCommands += ' M' + self.initialData[0].positionX + ' ' + self.initialData[0].positionY - console.log('DrawBoard[IndexPage] initDraw pathCommands=' + self.pathCommands) - - } else { - self.pathCommands += ' L' + self.initialData[0].positionX + ' ' + self.initialData[0].positionY - console.log('DrawBoard[IndexPage] initDraw pathCommands=' + self.pathCommands) - } - self.initialData.shift(); - - if (self.initialData.length < 1) { - clearInterval(self.intervalID); - self.intervalID = 0; + this.initialData.forEach((point)=>{ + if(point.isFirstPosition) { + this.context.beginPath() + this.context.lineWidth = 4 + this.context.lineJoin = 'miter' + this.context.moveTo(point.positionX, point.positionY) + } else{ + this.context.lineTo(point.positionX, point.positionY) + if(point.isEndPosition) { + this.context.stroke() + console.log('DrawBoard[IndexPage] initDraw context.stroke') + } } - }, 10); + }) } // 轨迹重绘制 redraw() { console.log('DrawBoard[IndexPage] redraw positionList= ' + JSON.stringify(this.positionList)) - this.pathCommands = DEfAULT_PATH_COMMAND + this.context.clearRect(0,0, this.context.width,this.context.height) if (this.positionList.length > 0 ) { this.positionList.forEach((num) => { console.log('DrawBoard[IndexPage] redraw num=') console.log('DrawBoard[IndexPage] redraw out isFirstPosition=' + num.isFirstPosition) if (num.isFirstPosition) { console.log('DrawBoard[IndexPage] redraw isFirstPosition=' + num.isFirstPosition) - this.pathCommands += ' M' + num.positionX + ' ' + num.positionY - console.log('DrawBoard[IndexPage] redraw pathCommands=' + this.pathCommands) + this.context.beginPath() + this.context.lineWidth = 4 + this.context.lineJoin = 'miter' + this.context.moveTo(num.positionX, num.positionY) + console.log('DrawBoard[IndexPage] redraw context.moveTo' + num.positionX+','+ num.positionY) } else { - this.pathCommands += ' L' + num.positionX + ' ' + num.positionY - console.log('DrawBoard[IndexPage] redraw pathCommands=' + this.pathCommands) + this.context.lineTo(num.positionX, num.positionY) + console.log('DrawBoard[IndexPage] redraw context.lineTo' + num.positionX+','+ num.positionY) + if(num.isEndPosition) { + this.context.stroke() + console.log('DrawBoard[IndexPage] redraw context.stroke') + } } }); } @@ -224,7 +224,6 @@ struct Index { for (let i = this.positionList.length - 1; i > -1; i--) { if (this.positionList[i].isFirstPosition) { this.positionList.pop(); - this.redraw(); break; } else { this.positionList.pop(); @@ -287,46 +286,46 @@ struct Index { case TouchType.Down: this.startX = event.touches[0].x this.startY = event.touches[0].y - this.pathCommands += ' M' + this.startX + ' ' + this.startY position.isFirstPosition = true; position.positionX = this.startX; position.positionY = this.startY; + position.isEndPosition = false + this.context.beginPath() + this.context.lineWidth = 4 + this.context.lineJoin = 'miter' + this.context.moveTo(this.startX, this.startY) this.pushData(position); break; case TouchType.Move: this.moveX = event.touches[0].x this.moveY = event.touches[0].y - this.pathCommands += ' L' + this.moveX + ' ' + this.moveY position.isFirstPosition = false; position.positionX = this.moveX; position.positionY = this.moveY; + position.isEndPosition = false + this.context.lineTo(this.moveX, this.moveY) this.pushData(position); break; case TouchType.Up: this.endX = event.touches[0].x this.endY = event.touches[0].y position.isFirstPosition = false; - position.positionX = this.moveX; - position.positionY = this.moveY; + position.positionX = this.endX; + position.positionY = this.endY; + position.isEndPosition = true + this.context.stroke() this.pushData(position); break; default: - break + break; } } pushData(position: any) { - this.isNeedSync = true; this.positionList.push(position); console.log('DrawBoard[IndexPage] pushData positionList 1 =' + JSON.stringify(this.positionList.length)); - let self = this; - if (this.intervalID === 0) { - this.intervalID = setInterval(function () { - if (self.isNeedSync) { - self.kvStoreModel.put(CHANGE_POSITION, JSON.stringify(self.positionList)); - console.log('DrawBoard[IndexPage] pushData positionList 2 =' + JSON.stringify(this.positionList.length)); - self.isNeedSync = false; - } - }, 100); + if(position.isEndPosition){ + this.kvStoreModel.put(CHANGE_POSITION, JSON.stringify(this.positionList)); + console.log('DrawBoard[IndexPage] pushData positionList 2 =' + JSON.stringify(this.positionList.length)); } } } \ No newline at end of file