diff --git a/.gitee/ISSUE_TEMPLATE/001_bug_report.yml b/.gitee/ISSUE_TEMPLATE/001_bug_report.yml new file mode 100644 index 0000000000000000000000000000000000000000..7107bc233c838b0738b53c3960912b09710fc480 --- /dev/null +++ b/.gitee/ISSUE_TEMPLATE/001_bug_report.yml @@ -0,0 +1,40 @@ +name: 缺陷反馈 | Bug +description: 当您发现了一个缺陷,需要向社区反馈时,请使用此模板。 +title: "[BUG] <标题>" +labels: [👀 needs triage, "Type: Bug"] +body: + - type: markdown + attributes: + value: | + 感谢您对 openvela 社区的支持与关注,欢迎反馈缺陷。 + + - type: textarea + attributes: + label: "重现问题的步骤" + description: "简洁地描述错误是什么,为什么您认为它是一个错误,以及如何重现它的步骤" + placeholder: | + 重现问题的步骤,可能包括日志和截图。 + 1. 步骤 1 + 2. 步骤 2 + validations: + required: true + + - type: dropdown + id: architecture + attributes: + label: Issue Architecture + multiple: true + options: + - "Arch: arm" + - "Arch: arm64" + - "Arch: x86_64" + validations: + required: true + + - type: markdown + attributes: + value: | + 提交前请确认您已遵循以下步骤: + - 确认问题在 [**dev**](../) 上可重现。 + - 遇到构建问题时运行 `make distclean`。 + - 搜索 [现有问题](./) diff --git a/.gitee/ISSUE_TEMPLATE/002_feature_request.yml b/.gitee/ISSUE_TEMPLATE/002_feature_request.yml new file mode 100644 index 0000000000000000000000000000000000000000..8b500152e7f080e30baa628bdc8c079b93be3d25 --- /dev/null +++ b/.gitee/ISSUE_TEMPLATE/002_feature_request.yml @@ -0,0 +1,34 @@ +name: 新需求 | Feature +description: 当您需要反馈或实现一个新需求时,使用此模板。 +title: "[FEATURE] <标题>" +body: + - type: markdown + attributes: + value: | + 感谢您对 openvela 社区的支持与关注。 + + - type: textarea + id: question-description + attributes: + label: 您的需求是否和问题相关? + description: 请简单描述问题,并提供issue链接。 + validations: + required: true + + - type: textarea + id: solution + attributes: + label: 请描述您想要的解决方案 + validations: + required: true + + - type: textarea + id: 替代方案 + attributes: + label: 请描述您考虑过的替代解决方案 + + - type: markdown + attributes: + value: | + 提交前请搜索 [现有功能需求](./) + diff --git a/.gitee/ISSUE_TEMPLATE/003_help.yml b/.gitee/ISSUE_TEMPLATE/003_help.yml new file mode 100644 index 0000000000000000000000000000000000000000..bde95adf3d07da0e687eef3d960a10b26b2a97a9 --- /dev/null +++ b/.gitee/ISSUE_TEMPLATE/003_help.yml @@ -0,0 +1,22 @@ +name: 问题咨询 +title: "[问题咨询]" +body: + - type: markdown + attributes: + value: | + 感谢您对 openvela 社区的支持与关注。 + + - type: textarea + id: question-description + attributes: + label: 描述 + description: 请解释您的问题的背景或上下文,这有助于其他人更好地理解您的问题或疑问。 + validations: + required: true + + - type: markdown + attributes: + value: | + 提交前请确认您已遵循以下步骤: + - 我已搜索 [openvela 文档](../),但未找到问题的答案。 + - 已搜索 [现有问题](./) diff --git a/.gitee/ISSUE_TEMPLATE/config.yml b/.gitee/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..3ba13e0cec6cbbfd462e9ebf529dd2093148cd69 --- /dev/null +++ b/.gitee/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.gitee/PULL_REQUEST_TEMPLATE.md b/.gitee/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000000000000000000000000000000000..cd8b2bc3a7690b37298e12d71c7b12189d26fae8 --- /dev/null +++ b/.gitee/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ +## 概要 + +*在此部分更新信息,说明更改的必要性、具体做了什么以及如何实现的,如果有新功能出现,请提供参考资料(依赖关系、类似问题和解决方案等)。* + +## 影响 + +*在此部分更新信息(如适用),说明更改如何影响用户、构建过程、硬件、文档、安全性、兼容性等。* + +## 测试 + +*在此部分更新信息,详细说明如何验证更改,使用什么主机进行构建(操作系统、CPU、编译器等),使用什么目标进行验证(架构、板子:配置等)。提供更改前后的构建和运行日志将非常有帮助。* \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000000000000000000000000000000000..99ea0de3c436dbab51e3599c804dfc16aa373762 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @xiaoxiang781216 @GUIDINGLI @terry0012 @jianglianfang diff --git a/.github/ISSUE_TEMPLATE/001_bug_report.yml b/.github/ISSUE_TEMPLATE/001_bug_report.yml new file mode 100644 index 0000000000000000000000000000000000000000..48e823db004f97019d883d13fd419084b650b4df --- /dev/null +++ b/.github/ISSUE_TEMPLATE/001_bug_report.yml @@ -0,0 +1,60 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +name: Bug report +description: Report a bug to improve openvela stability +title: "[BUG] " +labels: [👀 needs triage, "Type: Bug"] +body: + - type: markdown + attributes: + value: | + Hello openvela Community member! Please keep things tidy by putting your post in the proper place: + + Reporting a bug: use this form. + Asking a question or getting help: use the [General Help](./new?assignees=&labels=Community%3A+Question&projects=&template=003_help.yml&title=%5BHELP%5D+%3Ctitle%3E) form. + Requesting a new feature: use the [Feature request](./new?assignees=&labels=Type%3A+Enhancement&projects=&template=002_feature_request.yml&title=%5BFEATURE%5D+%3Ctitle%3E) form. + - type: textarea + attributes: + label: "Description / Steps to reproduce the issue" + description: "A clear and concise description of what the bug is, and why you consider it to be a bug, and steps for how to reproduce it" + placeholder: | + A description with steps to reproduce the issue. + May include logs, images, or videos. + 1. Step 1 + 2. Step 2 + validations: + required: true + + - type: dropdown + id: architecture + attributes: + label: Issue Architecture + description: What architecture(s) are you seeing the problem on? + multiple: true + options: + - "[Arch: arm]" + - "[Arch: arm64]" + - "[Arch: x86_64]" + validations: + required: true + + - type: markdown + attributes: + value: | + ### Before You Submit + + Please verify that you've followed these steps: + - Confirm the problem is reproducible on [**dev**](../). + - Run `make distclean` when encountering build issues. + - Search [existing issues](./) (including [closed](./?q=is%3Aissue+is%3Aclosed)) + diff --git a/.github/ISSUE_TEMPLATE/002_feature_request.yml b/.github/ISSUE_TEMPLATE/002_feature_request.yml new file mode 100644 index 0000000000000000000000000000000000000000..d5f410895687730a42836aa65c321c13e59100c5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/002_feature_request.yml @@ -0,0 +1,55 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +name: Feature request +description: Request an enhancement for openvela +title: "[FEATURE] <title>" +labels: ["Type: Enhancement"] +body: + - type: markdown + attributes: + value: | + Hello openvela Community member! Please keep things tidy by putting your post in the proper place: + + Requesting a new feature: use this form. + Asking a question or getting help: use the [General Help](./new?assignees=&labels=Community%3A+Question&projects=&template=003_help.yml&title=%5BHELP%5D+%3Ctitle%3E) form. + Reporting a bug: use the [Bug report](./new?assignees=&labels=%F0%9F%91%80+needs+triage%2CType%3A+Bug&projects=&template=001_bug_report.yml&title=%5BBUG%5D+%3Ctitle%3E) form. + + - type: textarea + id: question-description + attributes: + label: Is your feature request related to a problem? Please describe. + description: Please provide a clear and concise description of what the problem is. Add relevant issue link. + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Describe the solution you'd like + description: Please provide a clear and concise description of what you want to happen. + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Describe alternatives you've considered + description: Please provide a clear and concise description of any alternative solutions or features you've considered. + + - type: markdown + attributes: + value: | + ### Before You Submit + + Please verify that you've followed these steps: + - Search [existing feature requests](./) (including [closed](./?q=is%3Aissue+is%3Aclosed)) diff --git a/.github/ISSUE_TEMPLATE/003_help.yml b/.github/ISSUE_TEMPLATE/003_help.yml new file mode 100644 index 0000000000000000000000000000000000000000..ff1e1bf627ad1674eef3a4e20ed808e416beb0ec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/003_help.yml @@ -0,0 +1,47 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +name: General Help +description: Get general support regarding openvela +title: "[HELP] <title>" +labels: ["Community: Question"] +body: + - type: markdown + attributes: + value: | + Hello openvela Community member! Please keep things tidy by putting your post in the proper place: + + Asking a question or getting help: use this form. + Reporting a bug: use the [Bug report](./new?assignees=&labels=%F0%9F%91%80+needs+triage%2CType%3A+Bug&projects=&template=001_bug_report.yml&title=%5BBUG%5D+%3Ctitle%3E) form. + Requesting a new feature: use the [Feature request](./new?assignees=&labels=Type%3A+Enhancement&projects=&template=002_feature_request.yml&title=%5BFEATURE%5D+%3Ctitle%3E) form + + - type: markdown + attributes: + value: | + ### Whether you're a beginner or an experienced developer, openvela Help is here to assist you with all your openvela questions and concerns. + + - type: textarea + id: question-description + attributes: + label: Description + description: Explain the background or context of your question. This helps others understand your problem or inquiry better. + validations: + required: true + + - type: markdown + attributes: + value: | + ### Before You Submit + + Please verify that you've followed these steps: + - I have searched [openvela Documentation](../) and didn't find an answer to my question. + - Search [existing issues](./) (including [closed](./?q=is%3Aissue+is%3Aclosed)) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..3ba13e0cec6cbbfd462e9ebf529dd2093148cd69 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000000000000000000000000000000000..cfe55eb61d3334245c04325ee92abc7fdd1ebc07 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,20 @@ +*Note: Please adhere to [Contributing Guidelines](https://github.com/open-vela/docs/blob/dev/CONTRIBUTING.md).* + +## Summary + +*Update this section with information on why change is necessary, + what it exactly does and how, if new feature shows up, provide + references (dependencies, similar problems and solutions), etc.* + +## Impact + +*Update this section, where applicable, on how change affects users, + build process, hardware, documentation, security, compatibility, etc.* + +## Testing + +*Update this section with details on how did you verify the change, + what Host was used for build (OS, CPU, compiler, ..), what Target was + used for verification (arch, board:config, ..), etc. Providing build + and runtime logs from before and after change is highly appreciated.* + diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml new file mode 100644 index 0000000000000000000000000000000000000000..4d987aabb43c2effff6197beea063b44a4ad9b08 --- /dev/null +++ b/.github/workflows/checkpatch.yml @@ -0,0 +1,14 @@ +# This is a basic workflow to help you get started with Actions + +name: checkpatch + +# Controls when the workflow will run +on: + pull_request: + types: [opened, reopened, synchronize] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + checkpatch: + uses: open-vela/public-actions/.github/workflows/checkpatch.yml@trunk + secrets: inherit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..7a034a21157b0ebc785fb5cca1a31013a9e86f3a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,14 @@ +# This is a basic workflow to help you get started with Actions + +name: CI + +# Controls when the workflow will run +on: + pull_request_target: + types: [opened, reopened, synchronize] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + ci: + uses: open-vela/public-actions/.github/workflows/ci.yml@trunk + secrets: inherit diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000000000000000000000000000000000..8f8fc3cb81bad3b121f1e6aa23009cea0ee6cd4f --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,11 @@ +name: 'Close stale issues and PR' +on: + schedule: + - cron: '30 1 * * *' + workflow_dispatch: # 允许手动触发 + +jobs: + stale: + uses: open-vela/public-actions/.github/workflows/stale.yml@trunk + secrets: inherit + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..a52a2693e9c604f6b374e62d2948a313bb78f9e1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.o +.built +.depend +.kconfig +/Kconfig +Make.dep \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..bbe81b91d4736f0e27422b1f19acdd99606d3161 --- /dev/null +++ b/LICENSE @@ -0,0 +1,10 @@ +Copyright (C) 2024 Xiaomi Corporation +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/Make.defs b/Make.defs new file mode 100644 index 0000000000000000000000000000000000000000..5ea525a1ca8d0611085a3bc2aaff928a3183f569 --- /dev/null +++ b/Make.defs @@ -0,0 +1,36 @@ +############################################################################ +# packages/Make.defs +# Adds selected frameworks to apps/ build +# +# Copyright (C) 2019 Xiaomi Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +include $(wildcard $(APPDIR)/packages/demos/*/Make.defs) \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..949c23fb3090746f69b966fd700eb2984b31e320 --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +############################################################################ +# packages/Makefile +# +# Copyright (C) 2019 Xiaomi Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +MENUDESC = "Demos" + +include $(APPDIR)/Directory.mk diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..5c56623c630f09bf617d755806c8d1be31d00852 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# Demo + +\[ English | [简体中文](README_zh-cn.md) \] + +## Overview + +This repository implements examples of openvela native application code. Developers can quickly get started with development based on our documentation and examples. + +## Usage Instructions + +- [Music Player](../../../docs/blob/dev/en/demo/Music_Player_Example.md) +- [Smart Band](../../../docs/blob/dev/en/demo/Smart_Band_Example.md) +- [Bicycle Computer](../../../docs/blob/dev/en/demo/X_Track.md) +- [Calculator](calculator/Readme.md) +- [Relation Calculator](relation_calculator/Readme.md) +- [Whackmole](Whackmole/Readme.md) diff --git a/README_zh-cn.md b/README_zh-cn.md new file mode 100644 index 0000000000000000000000000000000000000000..813367c3240bc1672f2c22fb7328e9137eebc4f3 --- /dev/null +++ b/README_zh-cn.md @@ -0,0 +1,16 @@ +# Demo + +\[ [English](README.md) | 简体中文 \] + +## 简介 + +该仓库实现了 openvela native应用代码示例,开发者可以根据我们的文档和示例,快速上手开发。 + +## 使用说明 + +- [音乐播放器](../../../docs/blob/dev/zh-cn/demo/Music_Player_Example_zh-cn.md) +- [智能手环](../../../docs/blob/dev/zh-cn/demo/Smart_Band_Example_zh-cn.md) +- [自行车码表](../../../docs/blob/dev/zh-cn/demo/X_Track_zh-cn.md) +- [计算器](calculator/Readme.md) +- [亲戚计算器](relation_calculator/Readme.md) +- [打地鼠](Whackmole/Readme.md) diff --git a/Whackmole/Kconfig b/Whackmole/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..8b7413aadc0255907702b9001f5476b64c34a3f2 --- /dev/null +++ b/Whackmole/Kconfig @@ -0,0 +1,14 @@ + +config LVX_USE_DEMO_WHACKMOLE + bool "Whackmole" + default n + help + Enable the demo Whackmole application. + +if LVX_USE_DEMO_WHACKMOLE +config LVX_WHACKMOLE_DATA_ROOT + string "Whackmole Data Root" + default "/sdcard" + help + Specify the data storage path for the Whackmole application. +endif diff --git a/Whackmole/Make.defs b/Whackmole/Make.defs new file mode 100644 index 0000000000000000000000000000000000000000..f53ccdb4037e12345703ad8bfa3bcdf7c1c5208c --- /dev/null +++ b/Whackmole/Make.defs @@ -0,0 +1,4 @@ + # Make.defs +ifneq ($(CONFIG_LVX_USE_DEMO_WHACKMOLE),) +CONFIGURED_APPS += $(APPDIR)/packages/demos/Whackmole +endif diff --git a/Whackmole/Makefile b/Whackmole/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..aa70edf53b46339509266ed119229746f066bcfb --- /dev/null +++ b/Whackmole/Makefile @@ -0,0 +1,15 @@ +# Makefile + + +include $(APPDIR)/Make.defs + +ifeq ($(CONFIG_LVX_USE_DEMO_WHACKMOLE), y) +PROGNAME = Whackmole +PRIORITY = 100 +STACKSIZE = 32768 +MODULE = $(CONFIG_LVX_USE_DEMO_WHACKMOLE) +CSRCS = Whackmole.c +MAINSRC = Whackmole_main.c +endif + +include $(APPDIR)/Application.mk diff --git a/Whackmole/Readme.md b/Whackmole/Readme.md new file mode 100644 index 0000000000000000000000000000000000000000..fd6a754290b38d01685cb7e2c171621e2da20f1c --- /dev/null +++ b/Whackmole/Readme.md @@ -0,0 +1,110 @@ +# 打地鼠 + +## 运行效果 + +![alt text](img/whackmole.gif) + +## 使用说明 + +### 配置项目 + +1. 切换到 openvela 仓库的根目录,执行如下命令来配置Whackmole。 + + 模拟器配置文件(defconfig)在 `vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap/` 目录下,使用 `build.sh` 配置和编译开发板的代码。 + + ```Bash + ./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap menuconfig + ``` + +2. 按下 `/` 键逐个搜索修改如下配置: + + ```Bash + LVX_USE_DEMO_WHACKMOLE=y + LVX_WHACKMOLE_DATA_ROOT="/data" + ``` +3. 如果页面显示不流畅,在menuconfig中将lv_cache_def_size设置为20000000 + +### 编译项目 + +```Bash +# 清理构建产物 +./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap distclean -j6 + +# 开始构建 +./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap -j6 +``` + +### 启动模拟器并推送资源 + +1. 切换到 openvela 仓库的根目录,启动模拟器: + + ```Bash + ./emulator.sh vela + ``` + +2. 使用模拟器支持的 ADB 将资源推送到设备,在 openvela 仓库的根目录下打开一个新的终端,输入 adb push 后跟文件路径,即可将资源传输到相应位置。 + + ```Bash + # 安装adb + sudo apt install android-tools-adb + + # 推送资源 + adb push packages/demos/Whackmole/pic /data/ + + ``` + +### 启动游戏 + +在模拟器的终端环境 `openvela-ap>` 中输入如下命令: + +```Bash +Whackmole +``` + +## 功能修改 + +首先需要明确,新功能开发时只需要在`Whackmole.c`中增加/修改功能,`Whackmole_main.c`可当作通用开发模板。 + +### 修改资源图片 + +在`pic`下的`hammer.png`,`mole.png`,`grassland.png` 对应的位置修改图片资源,并重新推送资源 `adb push packages/demos/Whackmole/pic /data/` + +```C +void init_whack_a_mole_game(lv_obj_t *parent) { + // Set random number seed + srand(time(NULL)); + R.images.hammer = ICONS_ROOT "/hammer.png"; + R.images.mole = ICONS_ROOT "/mole.png"; + R.images.grassland = ICONS_ROOT "/grassland.png"; + ... +} +``` + +### 修改游戏难度值 + +在此游戏中,当游戏时间game_time小于40时,设置地鼠出现的定时器周期为800毫秒;如果小于20,则设为600毫秒。 + +```C +// Gophers appear randomly +static void pop_random_mole(lv_timer_t *timer) { + for (int i = 0; i < 9; i++) { + lv_obj_add_flag(moles[i], LV_OBJ_FLAG_HIDDEN); + } + int show_count = rand() % 2 + 1; + for (int i = 0; i < show_count; i++) { + int mole_idx = rand() % 9; + lv_obj_clear_flag(moles[mole_idx], LV_OBJ_FLAG_HIDDEN); + } + + // Adjust the frequency of gophers + if (game_time < 40) { + lv_timer_set_period(timer, 800); + } + if (game_time < 20) { + lv_timer_set_period(timer, 600); + } +} +``` +如果我们想要调整游戏难度,可以通过增大或减小定时器周期来实现。 + + diff --git a/Whackmole/Whackmole.c b/Whackmole/Whackmole.c new file mode 100644 index 0000000000000000000000000000000000000000..a921f95d59ae6824b0451a405c8b54cda690b4f5 --- /dev/null +++ b/Whackmole/Whackmole.c @@ -0,0 +1,346 @@ +#include "Whackmole.h" +#include "src/display/lv_display.h" +#include "src/font/lv_font.h" +#include <lvgl/lvgl.h> +#include <stdbool.h> +#include <stdio.h> // for snprintf +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#define GAME_TIME 30 + +static lv_obj_t* game_screen = NULL; +static lv_obj_t* moles[9]; +static lv_obj_t* score_label; +static lv_obj_t* time_label; +static lv_obj_t* hit_effect = NULL; +static lv_obj_t* hammer_cursor = NULL; + +typedef struct { + int x; + int y; +} hole_pos_t; + +static hole_pos_t hole_positions[9] = { { 210, 210 }, { 550, 210 }, { 900, 210 }, + { 150, 380 }, { 550, 380 }, { 900, 380 }, + { 150, 560 }, { 550, 560 }, { 950, 560 } }; + +static int score = 0; +static int game_time = GAME_TIME; +static lv_timer_t* game_timer = NULL; +static lv_timer_t* mole_timer = NULL; +static void start_game(lv_event_t* e); +static void delete_effect_cb(lv_timer_t* timer); +static void mole_click_event(lv_event_t* e); +static void update_game_timer(lv_timer_t* timer); +static void pop_random_mole(lv_timer_t* timer); +static void pointer_event_cb(lv_event_t* e); +struct resource_s R; + +static void end_msg_event_cb(lv_event_t* e) +{ + lv_obj_t* btn = lv_event_get_target(e); + lv_obj_t* footer = lv_obj_get_parent(btn); + lv_obj_t* mbox = lv_obj_get_parent(footer); + lv_obj_delete(mbox); + const char* txt = (const char*)lv_event_get_user_data(e); + if (strcmp(txt, "Again") == 0) { + start_game(NULL); + } +} +static bool init_resource(void) +{ + // Fonts + R.fonts.size_14.normal = lv_freetype_font_create( + FONTS_ROOT "/MiSans-Normal.ttf", LV_FREETYPE_FONT_RENDER_MODE_BITMAP, 14, + LV_FREETYPE_FONT_STYLE_NORMAL); + R.fonts.size_22.bold = lv_freetype_font_create( + FONTS_ROOT "/MiSans-Normal.ttf", LV_FREETYPE_FONT_RENDER_MODE_BITMAP, 22, + LV_FREETYPE_FONT_STYLE_NORMAL); + if (R.fonts.size_14.normal == NULL || R.fonts.size_22.bold == NULL || R.fonts.size_24.normal == NULL || R.fonts.size_28.normal == NULL || R.fonts.size_60.bold == NULL) { + return false; + } + + // Styles + lv_style_init(&R.styles.button_default); + lv_style_init(&R.styles.button_pressed); + lv_style_set_opa(&R.styles.button_default, LV_OPA_COVER); + lv_style_set_opa(&R.styles.button_pressed, LV_OPA_70); + + return true; +} + +// INIT +void init_whack_a_mole_game(lv_obj_t* parent) +{ + // Set random number seed + srand(time(NULL)); + R.images.hammer = ICONS_ROOT "/hammer.png"; + R.images.mole = ICONS_ROOT "/mole.png"; + R.images.grassland = ICONS_ROOT "/grassland.png"; + // create game screen + game_screen = lv_image_create(parent); + lv_image_set_src(game_screen, R.images.grassland); + lv_obj_set_align(game_screen, LV_ALIGN_CENTER); + lv_obj_set_size(game_screen, LV_PCT(100), LV_PCT(100)); + lv_obj_align(game_screen, LV_ALIGN_CENTER, 0, 0); + + // Disable scroll and click attributes + lv_obj_remove_flag(game_screen, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_remove_flag(game_screen, LV_OBJ_FLAG_CLICKABLE); + lv_obj_remove_flag(game_screen, LV_OBJ_FLAG_SCROLL_ELASTIC); + lv_obj_remove_flag(game_screen, LV_OBJ_FLAG_SCROLL_MOMENTUM); + + lv_obj_move_background(game_screen); + lv_obj_move_to_index(game_screen, 0); + // Add pointer events to track mouse position + lv_obj_add_event_cb(game_screen, pointer_event_cb, LV_EVENT_PRESSED, NULL); + lv_obj_add_event_cb(game_screen, pointer_event_cb, LV_EVENT_PRESSING, NULL); + + static const lv_style_prop_t props[] = { LV_STYLE_TRANSFORM_ROTATION, 0 }; + /* A special transition when going to pressed state + * Make it slow (500 ms) but start without delay*/ + static lv_style_transition_dsc_t trans_pr; + lv_style_transition_dsc_init(&trans_pr, props, lv_anim_path_linear, 150, 0, + NULL); + + // hammer cursor + hammer_cursor = lv_image_create(game_screen); + lv_image_set_src(hammer_cursor, R.images.hammer); + lv_obj_add_flag(hammer_cursor, LV_OBJ_FLAG_HIDDEN); + lv_obj_set_style_image_recolor(hammer_cursor, lv_color_black(), 0); + lv_obj_set_style_image_recolor_opa(hammer_cursor, LV_OPA_30, 0); + lv_obj_set_style_transform_scale_x(hammer_cursor, 60, 0); + lv_obj_set_style_transform_scale_y(hammer_cursor, 60, 0); + lv_obj_set_style_transform_rotation(hammer_cursor, 0, LV_STATE_DEFAULT); + lv_obj_set_style_transform_rotation(hammer_cursor, 300, LV_STATE_PRESSED); + lv_obj_set_style_transition(hammer_cursor, &trans_pr, LV_STATE_DEFAULT); + lv_obj_set_style_transition(hammer_cursor, &trans_pr, LV_STATE_PRESSED); + lv_obj_add_flag(hammer_cursor, LV_OBJ_FLAG_CLICKABLE); + lv_obj_remove_flag(hammer_cursor, LV_OBJ_FLAG_IGNORE_LAYOUT); + + // title + lv_obj_t* title = lv_label_create(game_screen); + lv_label_set_text(title, "Whackmole"); + lv_obj_set_style_text_font(title, R.fonts.size_22.bold, 0); + lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0); + lv_obj_set_align(title, LV_ALIGN_TOP_MID); + lv_obj_set_pos(title, 0, 10); + + // score display + score_label = lv_label_create(game_screen); + lv_label_set_text_fmt(score_label, "score: %d", score); + lv_obj_set_style_text_font(score_label, R.fonts.size_22.bold, 0); + lv_obj_set_style_text_color(score_label, lv_color_hex(0xFFFFFF), 0); + lv_obj_set_align(score_label, LV_ALIGN_TOP_LEFT); + + lv_obj_set_pos(score_label, 20, 10); + // Time display + time_label = lv_label_create(game_screen); + lv_label_set_text_fmt(time_label, "time: %d", game_time); + lv_obj_set_style_text_font(time_label, R.fonts.size_22.bold, 0); + lv_obj_set_style_text_color(time_label, lv_color_hex(0xFFFFFF), 0); + lv_obj_set_align(time_label, LV_ALIGN_TOP_RIGHT); + lv_obj_set_pos(time_label, -20, 10); + + for (int i = 0; i < 9; i++) { + lv_obj_t* hole = lv_obj_create(game_screen); + lv_obj_set_size(hole, 200, 150); + lv_obj_set_pos(hole, hole_positions[i].x - 30, hole_positions[i].y + 5); + lv_obj_set_style_bg_color(hole, lv_color_hex(0x000000), 0); + lv_obj_set_style_radius(hole, 25, 0); + lv_obj_set_style_border_width(hole, 0, 0); + lv_obj_set_style_shadow_width(hole, 0, 0); + lv_obj_set_style_shadow_color(hole, lv_color_hex(0x000000), 0); + lv_obj_set_style_shadow_opa(hole, LV_OPA_30, 0); + lv_obj_set_style_bg_opa(hole, LV_OPA_TRANSP, + 0); // Set background to transparent + + lv_obj_move_background(hole); + lv_obj_move_to_index(hole, 0); + // create moles + moles[i] = lv_image_create(hole); + lv_image_set_src(moles[i], R.images.mole); + // lv_obj_set_size(moles[i], LV_PCT(100), LV_PCT(100)); + lv_obj_set_align(moles[i], LV_ALIGN_CENTER); + lv_obj_set_style_image_recolor(moles[i], lv_color_black(), 0); + lv_obj_set_style_image_recolor_opa(moles[i], LV_OPA_30, 0); + lv_obj_set_style_transform_scale_x(hammer_cursor, 40, 0); + lv_obj_set_style_transform_scale_y(hammer_cursor, 40, 0); + lv_obj_add_flag(moles[i], LV_OBJ_FLAG_HIDDEN); + lv_obj_set_style_image_opa(moles[i], LV_OPA_COVER, 0); + lv_obj_set_style_image_recolor_opa(moles[i], LV_OPA_0, 0); + lv_obj_set_style_transform_scale(moles[i], 200, 0); + lv_obj_add_flag(moles[i], LV_OBJ_FLAG_CLICKABLE); + lv_obj_set_style_image_recolor_opa(moles[i], 0, 0); + lv_obj_move_foreground(moles[i]); + + // click event + lv_obj_add_event_cb(moles[i], mole_click_event, LV_EVENT_CLICKED, NULL); + // Hover event + lv_obj_add_event_cb(moles[i], pointer_event_cb, LV_EVENT_PRESSED, NULL); + lv_obj_add_event_cb(moles[i], pointer_event_cb, LV_EVENT_PRESSING, NULL); + lv_obj_add_event_cb(moles[i], pointer_event_cb, LV_EVENT_RELEASED, NULL); + } + + // start button + lv_obj_t* start_btn = lv_btn_create(game_screen); + lv_obj_set_size(start_btn, 120, 40); + lv_obj_set_align(start_btn, LV_ALIGN_BOTTOM_MID); + lv_obj_set_pos(start_btn, 0, -20); + lv_obj_add_event_cb(start_btn, start_game, LV_EVENT_CLICKED, NULL); + lv_obj_set_style_bg_color(start_btn, lv_color_hex(0x228B22), 0); + lv_obj_t* btn_label = lv_label_create(start_btn); + lv_label_set_text(btn_label, "START"); + lv_obj_set_style_text_font(start_btn, R.fonts.size_22.bold, 0); + lv_obj_set_style_text_color(start_btn, lv_color_hex(0xFFFFFF), 0); + lv_obj_center(btn_label); + lv_obj_move_foreground(hammer_cursor); + lv_obj_move_foreground(start_btn); + lv_obj_move_foreground(score_label); + lv_obj_move_foreground(time_label); + lv_obj_move_foreground(title); +} +static void pointer_event_cb(lv_event_t* e) +{ + lv_event_code_t code = lv_event_get_code(e); + lv_point_t pos; + lv_indev_t* indev = lv_indev_active(); + if (indev == NULL) + return; + lv_indev_get_point(indev, &pos); + lv_obj_remove_flag(hammer_cursor, LV_OBJ_FLAG_HIDDEN); + lv_obj_set_pos(hammer_cursor, pos.x - 25, pos.y - 25); + + if (code == LV_EVENT_PRESSED) { + lv_obj_set_state(hammer_cursor, LV_STATE_PRESSED, true); + } else if (code == LV_EVENT_PRESSING) { + } else { + lv_obj_set_state(hammer_cursor, LV_STATE_PRESSED, false); + } +} + +// start game +static void start_game(lv_event_t* e) +{ + score = 0; + game_time = GAME_TIME; + lv_label_set_text_fmt(score_label, "score: %d", score); + lv_label_set_text_fmt(time_label, "time: %d", game_time); + lv_obj_set_style_text_font(game_screen, R.fonts.size_22.bold, 0); + lv_obj_set_style_text_color(game_screen, lv_color_hex(0xFFFFFF), 0); + lv_obj_set_style_text_font(game_screen, R.fonts.size_22.bold, 0); + lv_obj_set_style_text_color(game_screen, lv_color_hex(0xFFFFFF), 0); + + for (int i = 0; i < 9; i++) { + lv_obj_add_flag(moles[i], LV_OBJ_FLAG_HIDDEN); + } + if (game_timer) { + lv_timer_delete(game_timer); + game_timer = NULL; + } + game_timer = lv_timer_create(update_game_timer, 1000, NULL); + + if (mole_timer) { + lv_timer_delete(mole_timer); + mole_timer = NULL; + } + mole_timer = lv_timer_create(pop_random_mole, 1000, NULL); +} + +// Update the game timer +static void update_game_timer(lv_timer_t* timer) +{ + game_time--; + lv_label_set_text_fmt(time_label, "time: %d", game_time); + if (game_time <= 0) { + lv_timer_delete(game_timer); + game_timer = NULL; + + if (mole_timer) { + lv_timer_delete(mole_timer); + mole_timer = NULL; + } + for (int i = 0; i < 9; i++) { + lv_obj_add_flag(moles[i], LV_OBJ_FLAG_HIDDEN); + } + lv_obj_t* end_msg = lv_msgbox_create(lv_screen_active()); + lv_msgbox_add_title(end_msg, "Game over!"); + lv_msgbox_add_text(end_msg, "Congratulations on completing game!"); + lv_obj_set_size(end_msg, 300, 120); + lv_msgbox_add_close_button(end_msg); + const char* btns[] = { "Again", "Close", NULL }; + for (int i = 0; btns[i]; i++) { + lv_obj_t* btn = lv_msgbox_add_footer_button(end_msg, btns[i]); + lv_obj_set_style_text_font(btn, R.fonts.size_14.normal, 0); + lv_obj_add_event_cb(btn, end_msg_event_cb, LV_EVENT_CLICKED, + (void*)btns[i]); + } + return; + } +} + +// Gophers appear randomly +static void pop_random_mole(lv_timer_t* timer) +{ + for (int i = 0; i < 9; i++) { + lv_obj_add_flag(moles[i], LV_OBJ_FLAG_HIDDEN); + } + int show_count = rand() % 2 + 1; + for (int i = 0; i < show_count; i++) { + int mole_idx = rand() % 9; + lv_obj_remove_flag(moles[mole_idx], LV_OBJ_FLAG_HIDDEN); + } + + // Adjust the frequency of gophers + if (game_time < 40) { + lv_timer_set_period(timer, 800); + } + if (game_time < 20) { + lv_timer_set_period(timer, 600); + } +} + +// Gopher click events +static void mole_click_event(lv_event_t* e) +{ + lv_obj_t* mole = lv_event_get_target(e); + LV_LOG_USER("clicked %d", lv_obj_has_flag(mole, LV_OBJ_FLAG_HIDDEN)); + // Bonus points when the gopher is visible + if (!lv_obj_has_flag(mole, LV_OBJ_FLAG_HIDDEN)) { + score++; + lv_label_set_text_fmt(score_label, "score: %d", score); + lv_obj_add_flag(mole, LV_OBJ_FLAG_HIDDEN); + if (hit_effect != NULL) { + lv_obj_delete(hit_effect); + hit_effect = NULL; + } + hit_effect = lv_obj_create(lv_obj_get_parent(mole)); + lv_obj_set_size(hit_effect, LV_PCT(100), LV_PCT(100)); + lv_obj_set_align(hit_effect, LV_ALIGN_CENTER); + lv_obj_set_pos(hit_effect, 0, 0); + lv_obj_set_style_bg_color(hit_effect, lv_color_hex(0xFFFF00), 0); + lv_obj_set_style_bg_opa(hit_effect, LV_OPA_50, 0); + lv_timer_create(delete_effect_cb, 300, hit_effect); + } +} + +// Delete click effects +static void delete_effect_cb(lv_timer_t* timer) +{ + lv_obj_t* effect = timer->user_data; + if (effect) { + lv_obj_delete(effect); + hit_effect = NULL; + } + lv_timer_delete(timer); +} + +// create app +void app_create(void) +{ + lv_obj_t* scr = lv_screen_active(); + init_resource(); + init_whack_a_mole_game(scr); +} diff --git a/Whackmole/Whackmole.h b/Whackmole/Whackmole.h new file mode 100644 index 0000000000000000000000000000000000000000..5a4562bb20a160f1e57e418431093acd0ee43786 --- /dev/null +++ b/Whackmole/Whackmole.h @@ -0,0 +1,68 @@ +#ifndef WHACKMOLE_H +#define WHACKMOLE_H +#include <lvgl/lvgl.h> +#define RES_ROOT CONFIG_LVX_WHACKMOLE_DATA_ROOT "/pic" +#define ICONS_ROOT RES_ROOT "/icons" +#define FONTS_ROOT RES_ROOT "/fonts" +void init_whack_a_mole_game(lv_obj_t* parent); +void app_create(void); +struct resource_s { + struct { + lv_obj_t* time; + lv_obj_t* date; + + lv_obj_t* player_group; + + lv_obj_t* volume_bar; + lv_obj_t* volume_bar_indic; + lv_obj_t* audio; + lv_obj_t* playlist_base; + + lv_obj_t* album_cover; + lv_obj_t* album_name; + lv_obj_t* album_artist; + + lv_obj_t* play_btn; + lv_obj_t* playback_group; + lv_obj_t* playback_progress; + lv_span_t* playback_current_time; + lv_span_t* playback_total_time; + + lv_obj_t* playlist; + } ui; + + struct { + struct { + lv_font_t* normal; + } size_14; + struct { + lv_font_t* bold; + } size_22; + struct { + lv_font_t* normal; + } size_24; + struct { + lv_font_t* normal; + } size_28; + struct { + lv_font_t* bold; + } size_60; + } fonts; + + struct { + lv_style_t button_default; + lv_style_t button_pressed; + lv_style_transition_dsc_t button_transition_dsc; + lv_style_transition_dsc_t transition_dsc; + } styles; + + struct { + const char* hammer; + const char* mole; + const char* grassland; + } images; +}; +struct ctx_s { + bool resource_healthy_check; +}; +#endif // WHACKMOLE_H diff --git a/Whackmole/Whackmole_main.c b/Whackmole/Whackmole_main.c new file mode 100644 index 0000000000000000000000000000000000000000..42752c4a5e8edbfccd6cb5e8034b5cf58c34a543 --- /dev/null +++ b/Whackmole/Whackmole_main.c @@ -0,0 +1,63 @@ +// include NuttX headers +#include <nuttx/config.h> +#include <unistd.h> +#include <uv.h> + +// include lvgl headers +#include <lvgl/lvgl.h> +#include "Whackmole.h" +static void lv_nuttx_uv_loop(uv_loop_t* loop, lv_nuttx_result_t* result) +{ + lv_nuttx_uv_t uv_info; + void* data; + + uv_loop_init(loop); + + lv_memset(&uv_info, 0, sizeof(uv_info)); + uv_info.loop = loop; + uv_info.disp = result->disp; + uv_info.indev = result->indev; +#ifdef CONFIG_UINPUT_TOUCH + uv_info.uindev = result->utouch_indev; +#endif + + data = lv_nuttx_uv_init(&uv_info); + uv_run(loop, UV_RUN_DEFAULT); + lv_nuttx_uv_deinit(&data); +} + +#include "Whackmole.h" + +int main(int argc, FAR char* argv[]) +{ + // init lvgl + lv_nuttx_dsc_t info; + lv_nuttx_result_t result; + uv_loop_t ui_loop; + lv_memset(&ui_loop, 0, sizeof(uv_loop_t)); + + if (lv_is_initialized()) { + LV_LOG_ERROR("LVGL already initialized! aborting."); + return -1; + } + + lv_init(); + + lv_nuttx_dsc_init(&info); + lv_nuttx_init(&info, &result); + + if (result.disp == NULL) { + LV_LOG_ERROR("lv_demos initialization failure!"); + return 1; + } + + app_create(); + + // refresh lvgl ui + lv_nuttx_uv_loop(&ui_loop, &result); + + lv_nuttx_deinit(&result); + lv_deinit(); + + return 0; +} diff --git a/Whackmole/img/whackmole.gif b/Whackmole/img/whackmole.gif new file mode 100644 index 0000000000000000000000000000000000000000..72526e4bfa41ef0ebc6b36785d23b07463c9a4dd Binary files /dev/null and b/Whackmole/img/whackmole.gif differ diff --git a/Whackmole/pic/fonts/MiSans-Normal.ttf b/Whackmole/pic/fonts/MiSans-Normal.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3199d464141d0599dd1269a3fef4264a829c6da0 Binary files /dev/null and b/Whackmole/pic/fonts/MiSans-Normal.ttf differ diff --git a/Whackmole/pic/fonts/MiSans-Semibold.ttf b/Whackmole/pic/fonts/MiSans-Semibold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..83c1a532632f582550d48bae117d758191ad1800 Binary files /dev/null and b/Whackmole/pic/fonts/MiSans-Semibold.ttf differ diff --git a/Whackmole/pic/icons/grassland.png b/Whackmole/pic/icons/grassland.png new file mode 100644 index 0000000000000000000000000000000000000000..bd460576b29f3d116cdf047d85b41a3d47b6940a Binary files /dev/null and b/Whackmole/pic/icons/grassland.png differ diff --git a/Whackmole/pic/icons/hammer.png b/Whackmole/pic/icons/hammer.png new file mode 100644 index 0000000000000000000000000000000000000000..6eca66c53e71fb8af46c1a9e739cf212850d9fdc Binary files /dev/null and b/Whackmole/pic/icons/hammer.png differ diff --git a/Whackmole/pic/icons/mole.png b/Whackmole/pic/icons/mole.png new file mode 100644 index 0000000000000000000000000000000000000000..4e61cbf4bef4e97213bc808c1304f913016aca90 Binary files /dev/null and b/Whackmole/pic/icons/mole.png differ diff --git a/bandx/Kconfig b/bandx/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..8691459f0ef9bdc00ac582b00ff860182b1d82d5 --- /dev/null +++ b/bandx/Kconfig @@ -0,0 +1,14 @@ +config LVX_USE_DEMO_BANDX + bool "Smart Band demo" + depends on GRAPHICS_LVGL && LV_USE_FRAGMENT && LV_USE_LIBPNG + default n + ---help--- + Enable build bandx Demo programs + +if LVX_USE_DEMO_BANDX + +config BANDX_BASE_PATH + string "Base path in bandx example" + default "/data" + +endif # LVX_USE_DEMO_BANDX diff --git a/bandx/Make.defs b/bandx/Make.defs new file mode 100644 index 0000000000000000000000000000000000000000..c08e4edf3f1096fc8787fbdb956d6d1a5e544806 --- /dev/null +++ b/bandx/Make.defs @@ -0,0 +1,18 @@ +# +# Copyright (C) 2023 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +ifneq ($(CONFIG_LVX_USE_DEMO_BANDX),) +CONFIGURED_APPS += $(APPDIR)/packages/demos/bandx +endif diff --git a/bandx/Makefile b/bandx/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..191bc9f6d8cf2e798fe1f8c323c255dd1c9c86e7 --- /dev/null +++ b/bandx/Makefile @@ -0,0 +1,14 @@ +include $(APPDIR)/Make.defs + +ifeq ($(CONFIG_LVX_USE_DEMO_BANDX), y) +PROGNAME += bandx +PRIORITY = 100 +STACKSIZE = 32768 +MODULE = $(CONFIG_LVX_USE_DEMO_BANDX) + +MAINSRC += bandx_demo_main.c +CSRCS += $(shell find -L ./ -name "*.c" | grep -v bandx_demo_main.c) + +endif + +include $(APPDIR)/Application.mk diff --git a/bandx/bandx_demo.c b/bandx/bandx_demo.c new file mode 100644 index 0000000000000000000000000000000000000000..0f07371e4f7826b4ce3c2035e4a0008efae0d3d0 --- /dev/null +++ b/bandx/bandx_demo.c @@ -0,0 +1,63 @@ +/** + * @file bandx_demo.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "bandx_demo.h" + +#include "page/page.h" +#include "resource/resource.h" + +/********************* + * DEFINES + *********************/ +#define ROOT_VER_RES 368 +#define ROOT_HOR_RES 194 + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void bandx_demo_create(void) +{ + lv_obj_set_style_bg_color(lv_scr_act(), lv_palette_main(LV_PALETTE_GREY), 0); + + /* prepare base root */ + static lv_obj_t* root; + root = lv_obj_create(lv_scr_act()); + lv_obj_remove_style_all(root); + lv_obj_set_size(root, ROOT_HOR_RES, ROOT_VER_RES); + lv_obj_center(root); + + resource_init(); + page_init(); + + lv_fragment_manager_t* manager = lv_fragment_manager_create(NULL); + + /* push first page */ + lv_fragment_t* page = page_create("dialplate", NULL); + lv_fragment_manager_push(manager, page, &root); +} + +/********************** + * STATIC FUNCTIONS + **********************/ diff --git a/bandx/bandx_demo.h b/bandx/bandx_demo.h new file mode 100644 index 0000000000000000000000000000000000000000..1a82f1e1efc0e56f605e872b0ee166c32edcbf1c --- /dev/null +++ b/bandx/bandx_demo.h @@ -0,0 +1,44 @@ +/** + * @file bandx_demo.h + * + */ + +#ifndef BANDX_DEMO_H +#define BANDX_DEMO_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include <lvgl/lvgl.h> + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Demo function for bandx. + */ +void bandx_demo_create(void); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*BANDX_DEMO_H*/ diff --git a/bandx/bandx_demo_main.c b/bandx/bandx_demo_main.c new file mode 100644 index 0000000000000000000000000000000000000000..da2184ef35ae3b76a67a4f9db756021cce68b81f --- /dev/null +++ b/bandx/bandx_demo_main.c @@ -0,0 +1,63 @@ +// include NuttX headers +#include <nuttx/config.h> +#include <unistd.h> +#include <uv.h> + +// include lvgl headers +#include <lvgl/lvgl.h> + +static void lv_nuttx_uv_loop(uv_loop_t* loop, lv_nuttx_result_t* result) +{ + lv_nuttx_uv_t uv_info; + void* data; + + uv_loop_init(loop); + + lv_memset(&uv_info, 0, sizeof(uv_info)); + uv_info.loop = loop; + uv_info.disp = result->disp; + uv_info.indev = result->indev; +#ifdef CONFIG_UINPUT_TOUCH + uv_info.uindev = result->utouch_indev; +#endif + + data = lv_nuttx_uv_init(&uv_info); + uv_run(loop, UV_RUN_DEFAULT); + lv_nuttx_uv_deinit(&data); +} + +#include "bandx_demo.h" + +int main(int argc, FAR char* argv[]) +{ + // init lvgl + lv_nuttx_dsc_t info; + lv_nuttx_result_t result; + uv_loop_t ui_loop; + lv_memset(&ui_loop, 0, sizeof(uv_loop_t)); + + if (lv_is_initialized()) { + LV_LOG_ERROR("LVGL already initialized! aborting."); + return -1; + } + + lv_init(); + + lv_nuttx_dsc_init(&info); + lv_nuttx_init(&info, &result); + + if (result.disp == NULL) { + LV_LOG_ERROR("bandx_demo initialization failure!"); + return 1; + } + + bandx_demo_create(); + + // refresh lvgl ui + lv_nuttx_uv_loop(&ui_loop, &result); + + lv_nuttx_deinit(&result); + lv_deinit(); + + return 0; +} diff --git a/bandx/hal/backlight.c b/bandx/hal/backlight.c new file mode 100755 index 0000000000000000000000000000000000000000..8fdf4087a8530c39050d348664e419601363d5eb --- /dev/null +++ b/bandx/hal/backlight.c @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/********************* + * INCLUDES + *********************/ +#include "backlight.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +static uint16_t backlight_value = 0; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +uint16_t backlight_get_value(void) +{ + return backlight_value; +} + +void backlight_set_value(int16_t val) +{ + backlight_value = val; +} + +/********************** + * STATIC FUNCTIONS + **********************/ diff --git a/bandx/hal/backlight.h b/bandx/hal/backlight.h new file mode 100755 index 0000000000000000000000000000000000000000..73c8c8ecbc682f66d8761bd8466f31d837c8e562 --- /dev/null +++ b/bandx/hal/backlight.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __BACKLIGHT_H +#define __BACKLIGHT_H + +/********************* + * INCLUDES + *********************/ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Get the current value of the backlight. + * @return The current backlight value. + */ +uint16_t backlight_get_value(void); + +/** + * Set the value of the backlight. + * @param val The value to set for the backlight. + */ +void backlight_set_value(int16_t val); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/bandx/hal/clock.c b/bandx/hal/clock.c new file mode 100755 index 0000000000000000000000000000000000000000..d019038a85d14d14ae057cf0bd13fea766de936f --- /dev/null +++ b/bandx/hal/clock.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/********************* + * INCLUDES + *********************/ + +#include "clock.h" +#include <time.h> + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void clock_get_value(clock_value_t* clock_value) +{ + struct tm* t; + time_t tt; + time(&tt); + t = localtime(&tt); + + clock_value->year = t->tm_year + 1900; + clock_value->month = t->tm_mon + 1; + clock_value->date = t->tm_mday; + clock_value->week = t->tm_wday; + clock_value->hour = t->tm_hour; + clock_value->min = t->tm_min; + clock_value->sec = t->tm_sec; + clock_value->ms = 0; +} + +void clock_set_value(const clock_value_t* clock_value) +{ +} + +/********************** + * STATIC FUNCTIONS + **********************/ diff --git a/bandx/hal/clock.h b/bandx/hal/clock.h new file mode 100755 index 0000000000000000000000000000000000000000..55a7a5cc97fc551a2859a2741371dfb285afe370 --- /dev/null +++ b/bandx/hal/clock.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __CLOCK_H +#define __CLOCK_H + +/********************* + * INCLUDES + *********************/ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + uint8_t hour; + uint8_t min; + uint8_t sec; + uint16_t ms; + uint16_t year; + uint8_t month; + uint8_t date; + uint8_t week; +} clock_value_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Get the current time value from the clock. + * @param clock_value Pointer to a structure to store the current clock value. + */ +void clock_get_value(clock_value_t* clock_value); + +/** + * Set the clock to a specific time value. + * @param clock_value Pointer to a structure of clock_value_t value to set. + */ +void clock_set_value(const clock_value_t* clock_value); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/bandx/hal/hal.h b/bandx/hal/hal.h new file mode 100755 index 0000000000000000000000000000000000000000..dfe456d92a3982c23c48f62061eb2b6a366cde0a --- /dev/null +++ b/bandx/hal/hal.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __HAL_H +#define __HAL_H + +/********************* + * INCLUDES + *********************/ + +#include "backlight.h" +#include "clock.h" +#include "imu.h" +#include "particle_sensor.h" +#include "power.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/********************** + * MACROS + **********************/ + +#endif diff --git a/bandx/hal/imu.c b/bandx/hal/imu.c new file mode 100644 index 0000000000000000000000000000000000000000..b7557487f5b4bfe135fe618bb12c751bcca44849 --- /dev/null +++ b/bandx/hal/imu.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/********************* + * INCLUDES + *********************/ + +#include "imu.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +static uint16_t step_cnt = 0; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +int16_t imu_get_steps(void) +{ + return step_cnt++; +} + +/********************** + * STATIC FUNCTIONS + **********************/ diff --git a/bandx/hal/imu.h b/bandx/hal/imu.h new file mode 100644 index 0000000000000000000000000000000000000000..177775e11ea3c02c79252fd02ddee0eb10548576 --- /dev/null +++ b/bandx/hal/imu.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __IMU_H +#define __IMU_H + +/********************* + * INCLUDES + *********************/ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Retrieve the number of steps recorded by the IMU. + * @return The number of steps as an integer value. + */ +int16_t imu_get_steps(void); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/bandx/hal/particle_sensor.c b/bandx/hal/particle_sensor.c new file mode 100755 index 0000000000000000000000000000000000000000..7865e4f72b04865db0c45f2ef829558b227c6922 --- /dev/null +++ b/bandx/hal/particle_sensor.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2020 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "particle_sensor.h" + +#include <stdint.h> +#include <stdlib.h> + +/********************* + * DEFINES + *********************/ + +#define HR_BEATS_MAX 1000 + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void particle_sensor_update_range(float beats); + +/********************** + * STATIC VARIABLES + **********************/ + +static int hr_last_beats = 600; +static float hr_beats_min = HR_BEATS_MAX; +static float hr_beats_max = 0; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +float particle_sensor_get_beats(void) +{ + float retval; + int breats = ((uint16_t)rand() % 600 + 400); + if (breats > hr_last_beats) { + hr_last_beats++; + } else if (breats < hr_last_beats) { + hr_last_beats--; + } + retval = hr_last_beats / 10.0f; + + particle_sensor_update_range(retval); + + return retval; +} + +bool particle_sensor_get_beats_range(float* min, float* max) +{ + if (hr_beats_min > HR_BEATS_MAX - 1 || hr_beats_max < 1) { + return false; + } + + *min = hr_beats_min; + *max = hr_beats_max; + return true; +} + +void particle_sensor_reset_beats_range(void) +{ + hr_beats_min = HR_BEATS_MAX; + hr_beats_max = 0; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void particle_sensor_update_range(float beats) +{ + if (beats < 1) { + return; + } + + if (beats < hr_beats_min) { + hr_beats_min = beats; + } + if (beats > hr_beats_max) { + hr_beats_max = beats; + } +} diff --git a/bandx/hal/particle_sensor.h b/bandx/hal/particle_sensor.h new file mode 100755 index 0000000000000000000000000000000000000000..e92b3dc5e87d80f83ed4d27723a06bb216744690 --- /dev/null +++ b/bandx/hal/particle_sensor.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __PARTICLE_SENSOR_H +#define __PARTICLE_SENSOR_H + +/********************* + * INCLUDES + *********************/ + +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Get the current heartbeats detected by the particle sensor. + * @return The current number of beats as a float value. + */ +float particle_sensor_get_beats(void); + +/** + * Get the range of heartbeats detected by the particle sensor. + * @param min Pointer to a float to store the minimum beat value. + * @param max Pointer to a float to store the maximum beat value. + * @return Return true if the operation was successful, otherwise return false. + */ +bool particle_sensor_get_beats_range(float* min, float* max); + +/** + * Reset the beats range for the particle sensor. + */ +void particle_sensor_reset_beats_range(void); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/bandx/hal/power.c b/bandx/hal/power.c new file mode 100755 index 0000000000000000000000000000000000000000..1d358f114418cd01d53cd80ab093c9711161fb81 --- /dev/null +++ b/bandx/hal/power.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2020 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/********************* + * INCLUDES + *********************/ + +#include "power.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +uint8_t power_get_battery_usage(void) +{ + return 80; +} + +bool power_get_battery_is_charging(void) +{ + return false; +} + +/********************** + * STATIC FUNCTIONS + **********************/ diff --git a/bandx/hal/power.h b/bandx/hal/power.h new file mode 100755 index 0000000000000000000000000000000000000000..34393808b5fcf0640c0cb6632bba87ff75163d3e --- /dev/null +++ b/bandx/hal/power.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __POWER_H +#define __POWER_H + +/********************* + * INCLUDES + *********************/ + +#include <stdbool.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Get the current battery usage percentage. + * @return The current battery usage as an unsigned integer value. + */ +uint8_t power_get_battery_usage(void); + +/** + * Check if the battery is currently charging. + * @return Return true if the battery is charging, otherwise return false. + */ +bool power_get_battery_is_charging(void); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/bandx/page/_template.c b/bandx/page/_template.c new file mode 100644 index 0000000000000000000000000000000000000000..66da47ba33ffda1b8fd3890141603dbefd9b6871 --- /dev/null +++ b/bandx/page/_template.c @@ -0,0 +1,100 @@ +/** + * @file _template.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "page.h" + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_fragment_t base; + lv_obj_t* label; +} page_ctx_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void on_page_construct(lv_fragment_t* self, void* args) +{ + LV_LOG_USER("self: %p args: %p", self, args); +} + +static void on_page_destruct(lv_fragment_t* self) +{ + LV_LOG_USER("self: %p", self); +} + +static void on_page_attached(lv_fragment_t* self) +{ + LV_LOG_USER("self: %p", self); +} + +static void on_page_detached(lv_fragment_t* self) +{ + LV_LOG_USER("self: %p", self); +} + +static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container) +{ + LV_LOG_USER("self: %p container: %p", self, container); + + lv_obj_t* root = lv_obj_create(container); + lv_obj_remove_style_all(root); + lv_obj_add_style(root, resource_get_style("root_def"), 0); + lv_obj_set_style_bg_color(root, lv_palette_main(LV_PALETTE_BLUE), 0); + return root; +} + +static void on_page_created(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_USER("self: %p obj: %p", self, obj); + + lv_obj_t* label = lv_label_create(obj); + lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_38"), 0); + lv_label_set_text(label, "SPORT"); + lv_obj_center(label); + + lv_obj_t* img = lv_img_create(obj); + lv_img_set_src(img, resource_get_img("sport")); + lv_obj_align(img, LV_ALIGN_TOP_MID, 0, 20); +} + +static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_USER("self: %p obj: %p", self, obj); +} + +static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_USER("self: %p obj: %p", self, obj); +} + +static bool on_page_event(lv_fragment_t* self, int code, void* user_data) +{ + LV_LOG_USER("self: %p code: %d user_data: %p", self, code, user_data); + return false; +} + +PAGE_CLASS_DEF(template); diff --git a/bandx/page/dialplate.c b/bandx/page/dialplate.c new file mode 100644 index 0000000000000000000000000000000000000000..d7b0735a920c016ecd94a70d6a3c359a0190cd43 --- /dev/null +++ b/bandx/page/dialplate.c @@ -0,0 +1,379 @@ +/** + * @file dialplate.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "page.h" + +#include "../hal/hal.h" + +/********************* + * DEFINES + *********************/ + +#define EVENT_CNT 1 + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + uint32_t color; + lv_coord_t y; + lv_coord_t bar_width; + lv_coord_t arc_radius; + const void* img_src; + lv_obj_t* label; + lv_obj_t* bar; + lv_obj_t* arc; +} color_bar_t; + +enum color_bar_index { + COLOR_BAR_WEATHER, + COLOR_BAR_STEP, + COLOR_BAR_HEART, + COLOR_BAR_MAX +}; + +typedef struct { + lv_fragment_t base; + lv_span_t* span_date; + lv_span_t* span_month; + lv_span_t* span_week; + lv_obj_t* bar_batt; + lv_obj_t* label_time_hour; + lv_obj_t* label_time_min; + lv_timer_t* timer_label_time_update; + lv_timer_t* timer_color_bar_update; + clock_value_t clock_value; + color_bar_t color_bar_grp[COLOR_BAR_MAX]; + lv_auto_event_data_t event_data[EVENT_CNT]; + lv_auto_event_t* auto_event; +} page_ctx_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ +static lv_obj_t* obj_batt_create(lv_obj_t* par) +{ + lv_obj_t* bar = lv_obj_create(par); + lv_obj_set_style_bg_color(bar, lv_color_white(), LV_PART_MAIN); + lv_obj_clear_flag(bar, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_style_border_width(bar, 0, LV_PART_MAIN); + lv_obj_set_style_radius(bar, 0, LV_PART_MAIN); + lv_obj_set_size(bar, lv_obj_get_self_width(par) / 2, lv_obj_get_self_height(par) - 2); + lv_obj_align(bar, LV_ALIGN_LEFT_MID, 2, 0); + + return bar; +} + +static void topbar_create(lv_obj_t* par) +{ + page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par); + + lv_obj_t* topbar = lv_obj_create(par); + lv_obj_set_size(topbar, LV_PCT(100), 25); + lv_obj_set_style_bg_color(topbar, lv_color_black(), LV_PART_MAIN); + lv_obj_set_style_border_width(topbar, 0, LV_PART_MAIN); + lv_obj_clear_flag(topbar, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_align(topbar, LV_ALIGN_TOP_MID, 0, 0); + + lv_obj_t* img1 = lv_img_create(topbar); + lv_img_set_src(img1, resource_get_img("icon_bluetooth")); + lv_obj_align(img1, LV_ALIGN_LEFT_MID, 20, 0); + + lv_obj_t* img2 = lv_img_create(topbar); + lv_img_set_src(img2, resource_get_img("icon_battery")); + lv_obj_set_style_pad_all(topbar, 0, LV_PART_MAIN); + lv_obj_align(img2, LV_ALIGN_RIGHT_MID, -20, 0); + + lv_obj_t* bar = obj_batt_create(img2); + ctx->bar_batt = bar; +} + +static void label_date_create(lv_obj_t* par) +{ + page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par); + + lv_obj_t* spangroup = lv_spangroup_create(par); + lv_obj_set_style_text_font(spangroup, resource_get_font(BANDX_BOLD_FONT "_23"), LV_PART_MAIN); + lv_obj_set_style_text_color(spangroup, lv_color_white(), LV_PART_MAIN); + lv_obj_align(spangroup, LV_ALIGN_TOP_MID, 0, 25); + + lv_span_t* span1 = lv_spangroup_new_span(spangroup); + lv_span_t* span2 = lv_spangroup_new_span(spangroup); + lv_span_t* span3 = lv_spangroup_new_span(spangroup); + lv_style_set_text_color(&span2->style, lv_color_hex(0xF15A24)); + + ctx->span_month = span1; + ctx->span_date = span2; + ctx->span_week = span3; +} + +static void label_time_create(lv_obj_t* par) +{ + page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par); + + lv_obj_t* label = lv_label_create(par); + lv_obj_set_style_text_font(label, resource_get_font(BANDX_BOLD_FONT "_110"), LV_PART_MAIN); + lv_obj_set_style_text_color(label, lv_color_white(), LV_PART_MAIN); + lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 33); + ctx->label_time_hour = label; + + lv_obj_t* img = lv_img_create(par); + lv_img_set_src(img, resource_get_img("num_shadow")); + lv_obj_set_pos(img, 16, 136); + + img = lv_img_create(par); + lv_img_set_src(img, resource_get_img("num_shadow")); + lv_obj_set_pos(img, 99, 136); + + label = lv_label_create(par); + lv_obj_set_style_text_font(label, resource_get_font(BANDX_BOLD_FONT "_110"), LV_PART_MAIN); + lv_obj_set_style_text_color(label, lv_color_hex(0xF15A24), LV_PART_MAIN); + lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 115); + ctx->label_time_min = label; +} + +static void label_time_update(lv_timer_t* timer) +{ + page_ctx_t* ctx = lv_timer_get_user_data(timer); + clock_get_value(&ctx->clock_value); + + char month_buf[32]; + char date_buf[32]; + const char* week_str[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; + int week_index = ctx->clock_value.week % ARRAY_SIZE(week_str); + + lv_snprintf(month_buf, sizeof(month_buf), "%02d-", ctx->clock_value.month); + lv_snprintf(date_buf, sizeof(date_buf), "%02d ", ctx->clock_value.date); + lv_span_set_text(ctx->span_month, month_buf); + lv_span_set_text(ctx->span_date, date_buf); + lv_span_set_text(ctx->span_week, week_str[week_index]); + + lv_label_set_text_fmt(ctx->label_time_hour, "%02d", ctx->clock_value.hour); + lv_label_set_text_fmt(ctx->label_time_min, "%02d", ctx->clock_value.min); +} + +static void color_bar_create(lv_obj_t* par, color_bar_t* color_bar, int len) +{ + for (int i = 0; i < len; i++) { + lv_color_t color = lv_color_hex(color_bar[i].color); + lv_coord_t y_pos = color_bar[i].y; + lv_coord_t bar_width = color_bar[i].bar_width; + lv_coord_t arc_size = color_bar[i].arc_radius * 2; + + lv_obj_t* bar = lv_bar_create(par); + lv_bar_set_value(bar, 100, LV_ANIM_OFF); + lv_obj_set_style_bg_color(bar, color, LV_PART_INDICATOR); + lv_obj_set_style_bg_color(bar, color, LV_PART_MAIN); + lv_obj_set_size(bar, bar_width, 32); + lv_obj_set_pos(bar, -12, y_pos); + + lv_obj_t* arc = lv_arc_create(par); + lv_obj_set_style_arc_color(arc, color, LV_PART_INDICATOR); + lv_obj_set_style_arc_width( + arc, + lv_obj_get_style_height(bar, 0), + LV_PART_INDICATOR); + lv_obj_set_style_bg_color(arc, color, LV_PART_KNOB); + lv_obj_set_style_pad_all(arc, 0, LV_PART_KNOB); + lv_obj_set_style_bg_opa(arc, LV_OPA_TRANSP, LV_PART_MAIN); + lv_obj_set_style_border_width(arc, 0, LV_PART_MAIN); + lv_arc_set_angles(arc, 270, 360); + lv_arc_set_bg_angles(arc, 270, 360); + lv_obj_set_size(arc, arc_size, arc_size); + lv_obj_clear_flag(arc, LV_OBJ_FLAG_CLICKABLE); + + lv_obj_align_to(arc, bar, LV_ALIGN_OUT_RIGHT_TOP, -arc_size / 2 - 10, 0); + + lv_obj_t* img = lv_img_create(bar); + lv_img_set_src(img, resource_get_img(color_bar[i].img_src)); + lv_obj_align(img, LV_ALIGN_LEFT_MID, 20, 0); + + if (i == COLOR_BAR_WEATHER) { + lv_obj_t* img_deg = lv_img_create(bar); + lv_img_set_src(img_deg, resource_get_img("centigrade")); + lv_obj_align(img_deg, LV_ALIGN_RIGHT_MID, 0, 0); + } + + lv_obj_t* label = lv_label_create(bar); + lv_obj_set_style_text_font( + label, + resource_get_font(BANDX_BOLD_FONT "_28"), + LV_PART_MAIN); + lv_obj_set_style_text_color( + label, + lv_color_white(), + LV_PART_MAIN); + lv_obj_align_to(label, img, LV_ALIGN_OUT_RIGHT_MID, 10, 0); + + lv_obj_move_foreground(bar); + + color_bar[i].bar = bar; + color_bar[i].label = label; + color_bar[i].arc = arc; + } +} + +static void color_bar_update(lv_timer_t* timer) +{ + page_ctx_t* ctx = lv_timer_get_user_data(timer); + + lv_label_set_text_fmt(ctx->color_bar_grp[COLOR_BAR_WEATHER].label, "15"); + lv_label_set_text_fmt(ctx->color_bar_grp[COLOR_BAR_STEP].label, "%d", imu_get_steps()); + lv_label_set_text_fmt( + ctx->color_bar_grp[COLOR_BAR_HEART].label, + "%d", + (int)particle_sensor_get_beats()); +} + +static void auto_event_create(page_ctx_t* ctx) +{ + lv_auto_event_data_t ae_grp[EVENT_CNT] = { + { &ctx->base.obj, LV_EVENT_LEAVE, 2000 }, + }; + for (int i = 0; i < EVENT_CNT; i++) { + ctx->event_data[i] = ae_grp[i]; + } + AUTO_EVENT_CREATE(ctx->auto_event, ctx->event_data); +} + +static void on_root_event(lv_event_t* e) +{ + lv_obj_t* root = lv_event_get_current_target_obj(e); + lv_event_code_t code = lv_event_get_code(e); + page_ctx_t* ctx = lv_obj_get_user_data(root); + + if (code == LV_EVENT_GESTURE) { + lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act()); + if (dir == LV_DIR_LEFT) { + lv_obj_send_event(root, LV_EVENT_LEAVE, NULL); + } + } else if (code == LV_EVENT_LEAVE) { + page_push(&ctx->base, "launcher", NULL); + page_set_last_page_dialplate(true); + } +} + +static void on_page_construct(lv_fragment_t* self, void* args) +{ + LV_LOG_INFO("self: %p args: %p", self, args); + + page_ctx_t* ctx = (page_ctx_t*)self; + + color_bar_t color_bar_grp[COLOR_BAR_MAX] = { + { .color = 0xF7931E, + .y = 245, + .bar_width = 130, + .arc_radius = 120, + .img_src = "weather" }, + + { .color = 0x3FA9F5, + .y = 285, + .bar_width = 130, + .arc_radius = 80, + .img_src = "step" }, + + { .color = 0xED1C24, + .y = 325, + .bar_width = 130, + .arc_radius = 40, + .img_src = "heart" }, + }; + + for (int i = 0; i < COLOR_BAR_MAX; i++) { + ctx->color_bar_grp[i] = color_bar_grp[i]; + } +} + +static void on_page_destruct(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static void on_page_attached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static void on_page_detached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container) +{ + LV_LOG_INFO("self: %p container: %p", self, container); + + lv_obj_t* root = lv_obj_create(container); + lv_obj_remove_style_all(root); + lv_obj_add_style(root, resource_get_style("root_def"), 0); + lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL); + lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE); + lv_obj_set_user_data(root, self); + return root; +} + +static void on_page_created(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + + page_ctx_t* ctx = (page_ctx_t*)self; + lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE); + clock_get_value(&ctx->clock_value); + + topbar_create(obj); + label_date_create(obj); + label_time_create(obj); + color_bar_create(obj, ctx->color_bar_grp, COLOR_BAR_MAX); + + ctx->timer_label_time_update = lv_timer_create(label_time_update, 100, ctx); + label_time_update(ctx->timer_label_time_update); + + ctx->timer_color_bar_update = lv_timer_create(color_bar_update, 1000, ctx); + color_bar_update(ctx->timer_color_bar_update); + auto_event_create(ctx); +} + +static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + + page_ctx_t* ctx = (page_ctx_t*)self; + lv_timer_del(ctx->timer_color_bar_update); + lv_timer_del(ctx->timer_label_time_update); + AUTO_EVENT_DELETE(ctx->auto_event); +} + +static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); +} + +static bool on_page_event(lv_fragment_t* self, int code, void* user_data) +{ + LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data); + return false; +} + +PAGE_CLASS_DEF(dialplate); diff --git a/bandx/page/flashlight.c b/bandx/page/flashlight.c new file mode 100755 index 0000000000000000000000000000000000000000..a08902a81ff58d93bafb041ffd41ddc6c32dca2c --- /dev/null +++ b/bandx/page/flashlight.c @@ -0,0 +1,159 @@ +/** + * @file flashlight.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "page.h" + +#include "../utils/lv_obj_ext_func.h" + +/********************* + * DEFINES + *********************/ + +#define EVENT_CNT 3 + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_fragment_t base; + lv_auto_event_data_t event_data[EVENT_CNT]; + lv_auto_event_t* auto_event; +} page_ctx_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void auto_event_create(page_ctx_t* ctx) +{ + lv_auto_event_data_t ae_grp[EVENT_CNT] = { + { &ctx->base.obj, LV_EVENT_REFRESH, 1000 }, + { &ctx->base.obj, LV_EVENT_REFRESH, 1000 }, + { &ctx->base.obj, LV_EVENT_LEAVE, 1000 }, + }; + for (int i = 0; i < EVENT_CNT; i++) { + ctx->event_data[i] = ae_grp[i]; + } + AUTO_EVENT_CREATE(ctx->auto_event, ctx->event_data); +} + +static void on_root_event(lv_event_t* e) +{ + lv_obj_t* root = lv_event_get_current_target_obj(e); + lv_event_code_t code = lv_event_get_code(e); + page_ctx_t* ctx = lv_obj_get_user_data(root); + + switch (code) { + case LV_EVENT_GESTURE: { + lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act()); + if (dir == LV_DIR_RIGHT) { + lv_obj_send_event(root, LV_EVENT_LEAVE, NULL); + } + } break; + case LV_EVENT_REFRESH: { + if (lv_obj_has_state(root, LV_STATE_CHECKED)) { + lv_obj_remove_state(root, LV_STATE_CHECKED); + } else { + lv_obj_add_state(root, LV_STATE_CHECKED); + } + } break; + case LV_EVENT_LEAVE: { + page_pop(&ctx->base); + } break; + default: + break; + } +} + +static void on_page_construct(lv_fragment_t* self, void* args) +{ + LV_LOG_INFO("self: %p args: %p", self, args); +} + +static void on_page_destruct(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static void on_page_attached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static void on_page_detached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container) +{ + LV_LOG_INFO("self: %p container: %p", self, container); + + lv_obj_t* root = lv_obj_create(container); + lv_obj_remove_style_all(root); + lv_obj_add_style(root, resource_get_style("root_def"), 0); + lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL); + lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE); + lv_obj_set_user_data(root, self); + return root; +} + +static void on_page_created(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + + page_ctx_t* ctx = (page_ctx_t*)self; + + lv_obj_add_flag(obj, LV_OBJ_FLAG_CHECKABLE); + + static const lv_style_prop_t props[] = { LV_STYLE_BG_COLOR, LV_STYLE_PROP_INV }; + static lv_style_transition_dsc_t dsc; + lv_style_transition_dsc_init(&dsc, props, lv_anim_path_ease_out, 200, 0, NULL); + + lv_obj_set_style_bg_color(obj, lv_color_white(), LV_STATE_DEFAULT); + lv_obj_set_style_bg_color(obj, lv_color_mix(lv_color_white(), lv_color_black(), 200), LV_STATE_CHECKED); + lv_obj_set_style_transition(obj, &dsc, LV_STATE_DEFAULT); + lv_obj_set_style_transition(obj, &dsc, LV_STATE_CHECKED); + auto_event_create(ctx); +} + +static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + page_ctx_t* ctx = (page_ctx_t*)self; + AUTO_EVENT_DELETE(ctx->auto_event); +} + +static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); +} + +static bool on_page_event(lv_fragment_t* self, int code, void* user_data) +{ + LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data); + return false; +} + +PAGE_CLASS_DEF(flashlight); diff --git a/bandx/page/heart_rate.c b/bandx/page/heart_rate.c new file mode 100644 index 0000000000000000000000000000000000000000..2ccc4ceeb43e478cbff539dacba4561e9a0e6c37 --- /dev/null +++ b/bandx/page/heart_rate.c @@ -0,0 +1,481 @@ +/** + * @file heart_rate.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "page.h" + +#include "../hal/hal.h" +#include "../utils/lv_obj_ext_func.h" + +/********************* + * DEFINES + *********************/ + +#define COLOR_BAR_CNT 4 +#define EVENT_CNT 4 + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + const char* text; + int value; + uint32_t color; + lv_coord_t y; +} color_bar_t; + +typedef struct { + lv_fragment_t base; + lv_obj_t* img_down; + lv_obj_t* img_up; + lv_obj_t* btn_rst; + lv_obj_t* img_heart; + lv_obj_t* chart_hr; + lv_obj_t* cont_hr; + lv_obj_t* label_hr_current; + lv_obj_t* label_hr_record_range; + lv_obj_t* label_hr_record_time; + lv_chart_series_t* chart_hr_ser1; + lv_timer_t* timer_label_hr_update; + lv_style_t color_bar_style; + lv_style_t label_hr_style1; + lv_style_t label_hr_style2; + color_bar_t color_bar_grp[COLOR_BAR_CNT]; + lv_auto_event_data_t event_data[EVENT_CNT]; + lv_auto_event_t* auto_event; +} page_ctx_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +#define CONT_HR_MOVE_DOWN(down, cont_hr, img_heart) \ + do { \ + LV_OBJ_ADD_ANIM( \ + cont_hr, \ + y, \ + (down) ? -PAGE_VER_RES : 0, LV_ANIM_TIME_DEFAULT); \ + img_heart_anim_enable(img_heart, !down); \ + } while (0) + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void label_hr_current_update(lv_timer_t* timer) +{ + page_ctx_t* ctx = lv_timer_get_user_data(timer); + + float hr_min, hr_max; + int hr_beats = (int)particle_sensor_get_beats(); + if (!particle_sensor_get_beats_range(&hr_min, &hr_max)) { + return; + } + + lv_label_set_text_fmt(ctx->label_hr_current, "%d", hr_beats); + lv_label_set_text_fmt(ctx->label_hr_record_range, "%d-%d", (int)hr_min, (int)hr_max); + lv_chart_set_next_value(ctx->chart_hr, ctx->chart_hr_ser1, hr_beats); +} + +static void img_heart_anim_enable(lv_obj_t* img_heart, bool en) +{ + if (en) { + lv_anim_t a; + lv_anim_init(&a); + lv_anim_set_var(&a, img_heart); + lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_img_set_zoom); + lv_anim_set_values(&a, 256, (int)(256 * 0.7f)); + lv_anim_set_time(&a, 300); + lv_anim_set_playback_time(&a, 300); + lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE); + lv_anim_set_repeat_delay(&a, 800); + lv_anim_set_path_cb(&a, lv_anim_path_overshoot); + lv_anim_start(&a); + } else { + lv_anim_del(img_heart, (lv_anim_exec_xcb_t)lv_img_set_zoom); + } +} + +static void cont_hr_create(lv_obj_t* par) +{ + page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par); + + lv_obj_t* obj = lv_obj_create(par); + lv_obj_remove_style_all(obj); + lv_obj_set_size(obj, PAGE_HOR_RES, PAGE_VER_RES * 2); + lv_obj_align(obj, LV_ALIGN_TOP_MID, 0, 0); + lv_obj_set_style_bg_color(obj, lv_color_black(), LV_PART_MAIN); + lv_obj_set_user_data(obj, ctx); + + ctx->cont_hr = obj; +} + +static void label_hr_current_create(lv_obj_t* par) +{ + page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par); + + lv_obj_t* img = lv_img_create(par); + lv_img_set_src(img, resource_get_img("heart_color")); + lv_obj_align(img, LV_ALIGN_TOP_MID, 0, 28); + + lv_obj_t* label1 = lv_label_create(par); + lv_obj_set_style_text_font(label1, resource_get_font(BANDX_REGULAR_FONT "_72"), LV_PART_MAIN); + lv_label_set_text(label1, "--"); + lv_obj_set_style_text_color(label1, lv_color_white(), LV_PART_MAIN); + lv_obj_set_style_text_align(label1, LV_ALIGN_CENTER, LV_PART_MAIN); + lv_obj_align(label1, LV_ALIGN_TOP_MID, 0, 95); + + lv_obj_t* label2 = lv_label_create(par); + lv_obj_set_style_text_font( + label2, + resource_get_font(BANDX_REGULAR_FONT "_20"), + LV_PART_MAIN); + lv_obj_set_style_text_color(label2, lv_palette_main(LV_PALETTE_GREY), LV_PART_MAIN); + lv_label_set_text(label2, "bpm"); + lv_obj_align_to(label2, label1, LV_ALIGN_OUT_RIGHT_BOTTOM, 10, -20); + + ctx->label_hr_current = label1; + ctx->img_heart = img; +} + +static void label_hr_record_create(lv_obj_t* par) +{ + page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par); + + lv_obj_t* label1 = lv_label_create(par); + lv_obj_add_style(label1, &ctx->label_hr_style1, LV_PART_MAIN); + lv_label_set_text(label1, "Recent updates"); + lv_obj_align(label1, LV_ALIGN_TOP_MID, 0, 165); + + lv_obj_t* label2 = lv_label_create(par); + lv_obj_add_style(label2, &ctx->label_hr_style2, LV_PART_MAIN); + lv_label_set_text(label2, "--:--"); + lv_obj_align(label2, LV_ALIGN_TOP_MID, 0, 192); + + lv_obj_t* label3 = lv_label_create(par); + lv_obj_add_style(label3, &ctx->label_hr_style1, LV_PART_MAIN); + lv_label_set_text(label3, "Range"); + lv_obj_align(label3, LV_ALIGN_TOP_MID, 0, 222); + + lv_obj_t* label4 = lv_label_create(par); + lv_obj_add_style(label4, &ctx->label_hr_style2, LV_PART_MAIN); + lv_label_set_text(label4, ""); + lv_obj_align(label4, LV_ALIGN_TOP_MID, 0, 246); + + ctx->label_hr_record_time = label2; + ctx->label_hr_record_range = label4; +} + +static void label_hr_record_time_update(lv_obj_t* label) +{ + clock_value_t clock_value; + clock_get_value(&clock_value); + lv_label_set_text_fmt(label, "%02d:%02d", clock_value.hour, clock_value.min); +} + +static void btn_rst_event_handler(lv_event_t* event) +{ + lv_event_code_t code = lv_event_get_code(event); + lv_obj_t* obj = lv_event_get_current_target_obj(event); + page_ctx_t* ctx = (page_ctx_t*)lv_event_get_user_data(event); + + if (code == LV_EVENT_CLICKED) { + particle_sensor_reset_beats_range(); + label_hr_record_time_update(ctx->label_hr_record_time); + + lv_obj_t* img = lv_obj_get_child(obj, 0); + lv_anim_t a; + lv_anim_init(&a); + lv_anim_set_var(&a, img); + lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_img_set_angle); + lv_anim_set_values(&a, 0, 3600); + lv_anim_set_time(&a, 1000); + lv_anim_set_path_cb(&a, lv_anim_path_ease_out); + lv_anim_start(&a); + } +} + +static void btn_rst_create(lv_obj_t* par) +{ + page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par); + + lv_obj_t* btn = lv_btn_create(par); + lv_obj_set_size(btn, 149, 44); + lv_obj_align(btn, LV_ALIGN_TOP_MID, 0, 279); + lv_obj_add_event(btn, btn_rst_event_handler, LV_EVENT_ALL, ctx); + lv_obj_set_style_bg_color( + btn, + lv_color_make(0xFF, 0x54, 0x2A), + LV_STATE_DEFAULT); + lv_obj_set_style_bg_color( + btn, + lv_palette_main(LV_PALETTE_GREY), + LV_STATE_PRESSED); + + lv_style_t* trans_style = resource_get_style("btn_trans"); + lv_obj_add_style(btn, trans_style, LV_STATE_PRESSED); + lv_obj_add_style(btn, trans_style, LV_STATE_DEFAULT); + lv_obj_set_style_radius(btn, 8, LV_PART_MAIN); + lv_obj_set_style_border_color(btn, lv_color_black(), LV_PART_MAIN); + lv_obj_set_style_border_width(btn, 0, LV_PART_MAIN); + lv_obj_set_style_outline_width(btn, 0, LV_PART_MAIN); + + lv_obj_t* img = lv_img_create(btn); + lv_img_set_src(img, resource_get_img("icon_reset")); + lv_obj_align_to(img, btn, LV_ALIGN_CENTER, 0, 0); + + ctx->btn_rst = btn; +} + +static void img_arrow_event_handler(lv_event_t* event) +{ + lv_event_code_t code = lv_event_get_code(event); + lv_obj_t* obj = lv_event_get_current_target_obj(event); + page_ctx_t* ctx = (page_ctx_t*)lv_event_get_user_data(event); + + if (code == LV_EVENT_CLICKED) { + bool is_down = (lv_strcmp(lv_img_get_src(obj), resource_get_img("arrow_down")) == 0); + CONT_HR_MOVE_DOWN(is_down, ctx->cont_hr, ctx->img_heart); + } +} + +static void img_arrow_create(lv_obj_t* par) +{ + page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par); + + lv_obj_t* img1 = lv_img_create(par); + lv_img_set_src(img1, resource_get_img("arrow_down")); + lv_obj_align(img1, LV_ALIGN_TOP_MID, 0, 339); + lv_obj_add_event(img1, img_arrow_event_handler, LV_EVENT_ALL, ctx); + lv_obj_add_flag(img1, LV_OBJ_FLAG_CLICKABLE); + ctx->img_down = img1; + + lv_obj_t* img2 = lv_img_create(par); + lv_img_set_src(img2, resource_get_img("arrow_up")); + lv_obj_align(img2, LV_ALIGN_TOP_MID, 0, PAGE_VER_RES + 12); + lv_obj_add_event(img2, img_arrow_event_handler, LV_EVENT_ALL, ctx); + lv_obj_add_flag(img2, LV_OBJ_FLAG_CLICKABLE); + ctx->img_up = img2; +} + +static void color_bar_create(lv_obj_t* par, color_bar_t* color_bar, int len) +{ + page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par); + + for (int i = 0; i < len; i++) { + lv_obj_t* bar = lv_bar_create(par); + lv_bar_set_value(bar, color_bar[i].value, LV_ANIM_OFF); + lv_obj_set_size(bar, 156, 10); + lv_obj_set_style_bg_color( + bar, + lv_color_hex(color_bar[i].color), + LV_PART_INDICATOR); + lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, PAGE_VER_RES + color_bar[i].y); + + lv_obj_t* label = lv_label_create(par); + lv_obj_add_style(label, &ctx->color_bar_style, LV_PART_MAIN); + lv_label_set_text_fmt(label, "%s %d%%", color_bar[i].text, color_bar[i].value); + lv_obj_align_to(label, bar, LV_ALIGN_OUT_TOP_LEFT, 0, -2); + } +} + +static void chart_hr_create(lv_obj_t* par) +{ + page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par); + + lv_obj_t* chart = lv_chart_create(par); + lv_obj_set_size(chart, 156, 87); + lv_obj_clear_flag(chart, LV_OBJ_FLAG_CLICKABLE); + lv_obj_align(chart, LV_ALIGN_TOP_MID, 0, PAGE_VER_RES + 36); + lv_chart_set_type(chart, LV_CHART_TYPE_LINE); + + lv_obj_set_style_bg_color( + chart, + lv_color_make(0x1A, 0x1A, 0x1A), + LV_PART_MAIN); + lv_obj_set_style_border_width(chart, 2, LV_PART_MAIN); + lv_obj_set_style_radius(chart, 5, LV_PART_MAIN); + lv_obj_set_style_border_color( + chart, + lv_color_make(0x66, 0x66, 0x66), + LV_PART_MAIN); + + lv_obj_set_style_bg_opa(chart, LV_OPA_50, LV_PART_ITEMS); + lv_obj_set_style_bg_grad_dir(chart, LV_GRAD_DIR_VER, LV_PART_ITEMS); + lv_obj_set_style_bg_main_stop(chart, 255, LV_PART_ITEMS); + lv_obj_set_style_bg_grad_stop(chart, 0, LV_PART_ITEMS); + lv_obj_set_style_line_width(chart, 1, LV_PART_ITEMS); + + lv_chart_set_div_line_count(chart, 0, 4); + lv_obj_set_style_line_color( + chart, + lv_color_make(0x66, 0x66, 0x66), + LV_PART_ITEMS); + lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, 50, 180); + lv_chart_set_point_count(chart, 100); + + ctx->chart_hr_ser1 = lv_chart_add_series(chart, lv_color_make(0xfc, 0x5c, 0x00), 0); + + ctx->chart_hr = chart; +} + +static void auto_event_create(page_ctx_t* ctx) +{ + lv_auto_event_data_t ae_grp[EVENT_CNT] = { + { &ctx->btn_rst, LV_EVENT_CLICKED, 2000 }, + { &ctx->img_down, LV_EVENT_CLICKED, 1000 }, + { &ctx->img_up, LV_EVENT_CLICKED, 1000 }, + { &ctx->base.obj, LV_EVENT_LEAVE, 1000 }, + }; + for (int i = 0; i < EVENT_CNT; i++) { + ctx->event_data[i] = ae_grp[i]; + } + + AUTO_EVENT_CREATE(ctx->auto_event, ctx->event_data); +} + +static void on_root_event(lv_event_t* e) +{ + lv_obj_t* root = lv_event_get_current_target_obj(e); + lv_event_code_t code = lv_event_get_code(e); + page_ctx_t* ctx = lv_obj_get_user_data(root); + + if (code == LV_EVENT_GESTURE) { + lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act()); + if (dir == LV_DIR_RIGHT) { + lv_obj_send_event(root, LV_EVENT_LEAVE, NULL); + } else if (dir == LV_DIR_BOTTOM) { + CONT_HR_MOVE_DOWN(false, ctx->cont_hr, ctx->img_heart); + } else if (dir == LV_DIR_TOP) { + CONT_HR_MOVE_DOWN(true, ctx->cont_hr, ctx->img_heart); + } + } else if (code == LV_EVENT_LEAVE) { + page_pop(&ctx->base); + } +} + +static void on_page_construct(lv_fragment_t* self, void* args) +{ + LV_LOG_INFO("self: %p args: %p", self, args); + + page_ctx_t* ctx = (page_ctx_t*)self; + + color_bar_t color_bar_grp[COLOR_BAR_CNT] = { + { "Relaxation", 45, 0x3FA9F5, 158 }, + { "Sports", 30, 0x7AC943, 212 }, + { "Competition", 20, 0xF15A24, 266 }, + { "Endurance", 5, 0xC1272D, 320 }, + }; + + for (int i = 0; i < COLOR_BAR_CNT; i++) { + ctx->color_bar_grp[i] = color_bar_grp[i]; + } + lv_style_init(&ctx->color_bar_style); + lv_style_set_text_font(&ctx->color_bar_style, resource_get_font(BANDX_REGULAR_FONT "_20")); + lv_style_set_text_color(&ctx->color_bar_style, lv_color_white()); + + lv_style_init(&ctx->label_hr_style1); + lv_style_set_text_font(&ctx->label_hr_style1, resource_get_font(BANDX_REGULAR_FONT "_20")); + lv_style_set_text_color(&ctx->label_hr_style1, lv_palette_main(LV_PALETTE_GREY)); + + lv_style_init(&ctx->label_hr_style2); + lv_style_set_text_font(&ctx->label_hr_style2, resource_get_font(BANDX_REGULAR_FONT "_20")); + lv_style_set_text_color(&ctx->label_hr_style2, lv_color_make(0xF1, 0x5A, 0x24)); +} + +static void on_page_destruct(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); + + page_ctx_t* ctx = (page_ctx_t*)self; + + lv_style_reset(&ctx->color_bar_style); + lv_style_reset(&ctx->label_hr_style1); + lv_style_reset(&ctx->label_hr_style2); +} + +static void on_page_attached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static void on_page_detached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container) +{ + LV_LOG_INFO("self: %p container: %p", self, container); + + lv_obj_t* root = lv_obj_create(container); + lv_obj_remove_style_all(root); + lv_obj_add_style(root, resource_get_style("root_def"), 0); + lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL); + lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE); + lv_obj_clear_flag(root, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_user_data(root, self); + return root; +} + +static void on_page_created(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + + page_ctx_t* ctx = (page_ctx_t*)self; + + cont_hr_create(obj); + label_hr_current_create(ctx->cont_hr); + label_hr_record_create(ctx->cont_hr); + label_hr_record_time_update(ctx->label_hr_record_time); + btn_rst_create(ctx->cont_hr); + img_arrow_create(ctx->cont_hr); + chart_hr_create(ctx->cont_hr); + color_bar_create(ctx->cont_hr, ctx->color_bar_grp, COLOR_BAR_CNT); + + ctx->timer_label_hr_update = lv_timer_create(label_hr_current_update, 1000, ctx); + + img_heart_anim_enable(ctx->img_heart, true); + auto_event_create(ctx); +} + +static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + + page_ctx_t* ctx = (page_ctx_t*)self; + + lv_timer_del(ctx->timer_label_hr_update); + img_heart_anim_enable(ctx->img_heart, false); + AUTO_EVENT_DELETE(ctx->auto_event); +} + +static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); +} + +static bool on_page_event(lv_fragment_t* self, int code, void* user_data) +{ + LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data); + return false; +} + +PAGE_CLASS_DEF(heart_rate); diff --git a/bandx/page/launcher.c b/bandx/page/launcher.c new file mode 100644 index 0000000000000000000000000000000000000000..578b20bb66d2d937c4fd3eee87848435fd9e2adb --- /dev/null +++ b/bandx/page/launcher.c @@ -0,0 +1,336 @@ +/** + * @file launcher.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "../utils/lv_obj_ext_func.h" +#include "page.h" + +/********************* + * DEFINES + *********************/ + +#define APP_ENTRY_CNT 7 +#define EVENT_CNT 3 + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + const void* name; + const char* text; + uint32_t bg_color; + lv_obj_t* icon; +} app_entry_t; + +typedef struct { + lv_fragment_t base; + lv_style_t cont_style; + lv_style_t icon_style; + lv_style_t label_style; + app_entry_t app_entry[APP_ENTRY_CNT]; + lv_timer_t* auto_show_timer; +} page_ctx_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +#define APP_ICON_SIZE 100 +#define APP_ICON_PAD_VER (APP_ICON_SIZE / 2) +#define APP_ICON_ANIM_TIME 200 + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void app_icon_click_anim(lv_obj_t* img, bool ispress) +{ + lv_anim_t a; + lv_anim_init(&a); + lv_anim_set_var(&a, img); + lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_img_set_zoom); + lv_anim_set_values( + &a, + lv_img_get_zoom(img), + ispress ? (int)(256 * 0.8f) : 256); + lv_anim_set_time(&a, APP_ICON_ANIM_TIME); + + lv_anim_set_path_cb(&a, ispress ? lv_anim_path_ease_in_out : lv_anim_path_overshoot); + + lv_anim_start(&a); +} + +static void on_app_icon_event(lv_event_t* e) +{ + page_ctx_t* ctx = lv_event_get_user_data(e); + lv_obj_t* icon = lv_event_get_current_target(e); + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t* img = lv_obj_get_child(icon, 0); + + switch (code) { + case LV_EVENT_PRESSED: + app_icon_click_anim(img, true); + break; + case LV_EVENT_RELEASED: + case LV_EVENT_PRESS_LOST: + app_icon_click_anim(img, false); + break; + case LV_EVENT_CLICKED: { + app_entry_t* entry = lv_obj_get_user_data(icon); + page_set_last_page_dialplate(false); + page_push(&ctx->base, entry->name, NULL); + } break; + default: + break; + } +} + +static void app_icon_create(page_ctx_t* ctx, lv_obj_t* par, app_entry_t* entry) +{ + lv_obj_t* cont = lv_obj_create(par); + lv_obj_remove_style_all(cont); + lv_obj_add_style(cont, &ctx->cont_style, 0); + lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(cont, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + lv_obj_t* icon = lv_obj_create(cont); + lv_style_t* trans_style = resource_get_style("btn_trans"); + lv_obj_remove_style_all(icon); + lv_obj_add_style(icon, &ctx->icon_style, 0); + + lv_obj_set_style_bg_color(icon, lv_palette_main(LV_PALETTE_GREY), LV_STATE_PRESSED); + lv_obj_set_style_transform_width(icon, 5, LV_STATE_PRESSED); + lv_obj_set_style_transform_height(icon, -5, LV_STATE_PRESSED); + lv_obj_add_style(icon, trans_style, LV_STATE_PRESSED); + lv_obj_add_style(icon, trans_style, LV_STATE_DEFAULT); + + lv_obj_set_style_bg_color(icon, lv_color_hex(entry->bg_color), 0); + lv_obj_set_user_data(icon, (void*)entry); + lv_obj_add_event(icon, on_app_icon_event, LV_EVENT_ALL, ctx); + lv_obj_clear_flag(icon, LV_OBJ_FLAG_SCROLLABLE); + + lv_obj_t* img = lv_img_create(icon); + lv_img_set_src(img, resource_get_img(entry->name)); + lv_obj_center(img); + + lv_obj_t* label = lv_label_create(cont); + lv_obj_add_style(label, &ctx->label_style, 0); + lv_label_set_text(label, entry->text); + entry->icon = icon; +} + +static void auto_show_update(lv_timer_t* timer) +{ + page_ctx_t* ctx = lv_timer_get_user_data(timer); + + static int current_show_app_index = -1; + + if (current_show_app_index >= APP_ENTRY_CNT) { + current_show_app_index = -1; + } + + enum state_type { IDLE, + FOCUS, + PRESSED, + PRESSED_LOST, + CLICK, + _END }; + static uint8_t state = IDLE; + if (page_is_last_page_dialplate()) { + current_show_app_index = 0; + page_set_last_page_dialplate(false); + } + + if (current_show_app_index >= 0 && current_show_app_index < APP_ENTRY_CNT) { + lv_obj_t* current_icon = ctx->app_entry[current_show_app_index].icon; + + switch (state) { + case IDLE: + break; + case FOCUS: + lv_obj_scroll_to_view_recursive(current_icon, LV_ANIM_ON); + lv_obj_send_event(current_icon, LV_EVENT_FOCUSED, ctx); + break; + case PRESSED: + lv_obj_send_event(current_icon, LV_EVENT_PRESSED, ctx); + break; + case PRESSED_LOST: + lv_obj_send_event(current_icon, LV_EVENT_PRESS_LOST, ctx); + break; + case CLICK: + if (lv_strcmp(ctx->app_entry[current_show_app_index].text, "Settings") == 0) { + current_show_app_index = -1; + } else { + current_show_app_index++; + } + lv_obj_send_event(current_icon, LV_EVENT_CLICKED, ctx); + break; + case _END: + break; + default: + break; + } + state++; + state %= _END; + } else { + lv_obj_send_event(ctx->base.obj, LV_EVENT_LEAVE, NULL); + } +} + +static void on_root_event(lv_event_t* e) +{ + lv_obj_t* root = lv_event_get_current_target_obj(e); + lv_event_code_t code = lv_event_get_code(e); + page_ctx_t* ctx = lv_obj_get_user_data(root); + + if (code == LV_EVENT_GESTURE) { + lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act()); + if (dir == LV_DIR_RIGHT) { + lv_obj_send_event(root, LV_EVENT_LEAVE, NULL); + } + } else if (code == LV_EVENT_LEAVE) { + page_pop(&ctx->base); + } +} + +static void on_page_construct(lv_fragment_t* self, void* args) +{ + LV_LOG_INFO("self: %p args: %p", self, args); + page_ctx_t* ctx = (page_ctx_t*)self; + + lv_style_init(&ctx->cont_style); + lv_style_set_width(&ctx->cont_style, LV_PCT(100)); + lv_style_set_height(&ctx->cont_style, LV_SIZE_CONTENT); + + lv_style_init(&ctx->icon_style); + lv_style_set_bg_opa(&ctx->icon_style, LV_OPA_COVER); + lv_style_set_size(&ctx->icon_style, APP_ICON_SIZE, APP_ICON_SIZE); + lv_style_set_radius(&ctx->icon_style, 10); + + lv_style_init(&ctx->label_style); + lv_style_set_text_font(&ctx->label_style, resource_get_font(BANDX_BOLD_FONT "_23")); + lv_style_set_text_color(&ctx->label_style, lv_color_white()); + + app_entry_t app_entry[APP_ENTRY_CNT] = { + { "heart_rate", "Heart", 0xFF542A }, + { "music", "Music", 0xEE4C84 }, + { "stop_watch", "StWatch", 0x3CFFA7 }, + { "flashlight", "Light", 0xFFB428 }, + { "sleep", "Sleep", 0x001BC8 }, + { "sport", "Sport", 0x9B5DFF }, + { "settings", "Settings", 0x0089FF }, + }; + + for (int i = 0; i < APP_ENTRY_CNT; i++) { + ctx->app_entry[i] = app_entry[i]; + } +} + +static void on_page_destruct(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); + page_ctx_t* ctx = (page_ctx_t*)self; + lv_style_reset(&ctx->cont_style); + lv_style_reset(&ctx->icon_style); + lv_style_reset(&ctx->label_style); +} + +static void on_page_attached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static void on_page_detached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container) +{ + LV_LOG_INFO("self: %p container: %p", self, container); + + lv_obj_t* root = lv_obj_create(container); + lv_obj_remove_style_all(root); + lv_obj_add_style(root, resource_get_style("root_def"), 0); + lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL); + lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE); + lv_obj_set_user_data(root, self); + return root; +} + +static void on_page_created(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + + page_ctx_t* ctx = (page_ctx_t*)self; + + /* root */ + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_row(obj, 20, 0); + lv_obj_set_style_pad_ver(obj, APP_ICON_PAD_VER, 0); + + /* apps */ + for (int i = 0; i < APP_ENTRY_CNT; i++) { + app_icon_create( + ctx, + obj, + &ctx->app_entry[i]); + } + + /* shadow image */ + { + lv_obj_t* img1 = lv_img_create(obj); + lv_img_set_src(img1, resource_get_img("icon_shadow_up")); + lv_obj_align(img1, LV_ALIGN_TOP_MID, 0, -APP_ICON_PAD_VER); + lv_obj_add_flag(img1, LV_OBJ_FLAG_FLOATING); + + lv_obj_t* img2 = lv_img_create(obj); + lv_img_set_src(img2, resource_get_img("icon_shadow_down")); + lv_obj_align(img2, LV_ALIGN_BOTTOM_MID, 0, APP_ICON_PAD_VER); + lv_obj_add_flag(img2, LV_OBJ_FLAG_FLOATING); + } + + if (page_get_autoshow_enable()) { + ctx->auto_show_timer = lv_timer_create(auto_show_update, 500, ctx); + } +} + +static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + page_ctx_t* ctx = (page_ctx_t*)self; + if (page_get_autoshow_enable()) { + lv_timer_del(ctx->auto_show_timer); + } +} + +static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); +} + +static bool on_page_event(lv_fragment_t* self, int code, void* user_data) +{ + LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data); + return false; +} + +PAGE_CLASS_DEF(launcher); diff --git a/bandx/page/music.c b/bandx/page/music.c new file mode 100644 index 0000000000000000000000000000000000000000..5a3bdda1dc1a84f25b982688624555ef007a6314 --- /dev/null +++ b/bandx/page/music.c @@ -0,0 +1,563 @@ +/** + * @file music.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "page.h" + +/********************* + * DEFINES + *********************/ + +#define MC_COLOR_GRAY lv_color_make(0x33, 0x33, 0x33) +#define MC_COLOR_PINK lv_color_make(0xB9, 0x00, 0x5E) +#define MC_COLOR_PINK_BG lv_color_make(0x17, 0x00, 0x0B) + +#define MUSIC_TIME_TO_MS(min, sec) ((min)*60 * 1000 + (sec)*1000) +#define MUSIC_INFO_CNT 3 +#define EVENT_CNT 9 + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + const char* name; + uint32_t time_ms; +} music_info_t; + +typedef struct { + lv_fragment_t base; + lv_style_t btn_style; + lv_style_t trans_style; + lv_obj_t* bar_volume; + lv_obj_t* arc_play; + lv_obj_t* obj_play; + lv_obj_t* label_music_info; + lv_obj_t* btn_volume_add; + lv_obj_t* btn_volume_reduce; + lv_obj_t* btn_music_ctrl_next; + lv_obj_t* btn_music_ctrl_prev; + lv_span_t* span_cur_time; + lv_span_t* span_end_time; + lv_timer_t* timer_music_update; + uint32_t music_current_time; + uint32_t music_end_time; + uint16_t music_current_index; + bool music_is_playing; + music_info_t music_info_grp[MUSIC_INFO_CNT]; + lv_auto_event_data_t event_data[EVENT_CNT]; + lv_auto_event_t* auto_event; +} page_ctx_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void on_root_event(lv_event_t* e) +{ + lv_obj_t* root = lv_event_get_target(e); + lv_event_code_t code = lv_event_get_code(e); + page_ctx_t* ctx = lv_obj_get_user_data(root); + + if (code == LV_EVENT_GESTURE) { + lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act()); + if (dir == LV_DIR_RIGHT) { + lv_obj_send_event(root, LV_EVENT_LEAVE, NULL); + } + } else if (code == LV_EVENT_LEAVE) { + page_pop(&ctx->base); + } +} + +static void bar_volume_set_value(int16_t value, lv_obj_t* bar_volume) +{ + lv_bar_set_value(bar_volume, value, LV_ANIM_ON); +} + +static void bar_volume_create(lv_obj_t* par) +{ + page_ctx_t* ctx = lv_obj_get_user_data(par); + + lv_obj_t* bar = lv_bar_create(par); + lv_obj_set_size(bar, 160, 3); + lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, 70); + lv_obj_set_style_bg_color(bar, MC_COLOR_PINK, LV_PART_MAIN); + lv_obj_set_style_bg_color(bar, MC_COLOR_PINK, LV_PART_INDICATOR); + + ctx->bar_volume = bar; +} + +static void btn_volume_event_handler(lv_event_t* event) +{ + lv_obj_t* obj = lv_event_get_target(event); + lv_event_code_t code = lv_event_get_code(event); + page_ctx_t* ctx = lv_event_get_user_data(event); + + if (code == LV_EVENT_CLICKED) { + lv_obj_t* img = lv_obj_get_child(obj, 0); + int16_t vol_value = lv_bar_get_value(ctx->bar_volume); + + if (lv_strcmp(lv_img_get_src(img), resource_get_img("icon_volume_add")) == 0) { + bar_volume_set_value(vol_value + 10, ctx->bar_volume); + } else { + bar_volume_set_value(vol_value - 10, ctx->bar_volume); + } + } +} + +static void btn_volume_create(lv_obj_t* par) +{ + page_ctx_t* ctx = lv_obj_get_user_data(par); + + lv_obj_t* btn1 = lv_btn_create(par); + lv_obj_set_size(btn1, 85, 48); + lv_obj_add_style(btn1, &ctx->btn_style, LV_PART_MAIN); + lv_obj_add_style(btn1, &ctx->trans_style, LV_STATE_DEFAULT); + lv_obj_add_style(btn1, &ctx->trans_style, LV_STATE_PRESSED); + lv_obj_set_style_bg_color(btn1, lv_color_white(), LV_STATE_PRESSED); + lv_obj_align(btn1, LV_ALIGN_TOP_MID, -lv_obj_get_style_width(btn1, 0) / 2 - 3, 10); + lv_obj_add_event(btn1, btn_volume_event_handler, LV_EVENT_ALL, ctx); + + lv_obj_t* btn2 = lv_btn_create(par); + lv_obj_set_size(btn2, 85, 48); + lv_obj_add_style(btn2, &ctx->btn_style, LV_PART_MAIN); + lv_obj_add_style(btn2, &ctx->trans_style, LV_STATE_DEFAULT); + lv_obj_add_style(btn2, &ctx->trans_style, LV_STATE_PRESSED); + lv_obj_set_style_bg_color(btn2, lv_color_white(), LV_STATE_PRESSED); + lv_obj_align(btn2, LV_ALIGN_TOP_MID, lv_obj_get_style_width(btn2, 0) / 2 + 3, 10); + lv_obj_add_event(btn2, btn_volume_event_handler, LV_EVENT_ALL, ctx); + + lv_obj_t* img1 = lv_img_create(btn1); + lv_img_set_src(img1, resource_get_img("icon_volume_reduce")); + lv_obj_set_style_image_recolor_opa(img1, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_image_recolor(img1, lv_color_black(), LV_PART_MAIN); + lv_obj_align(img1, LV_ALIGN_CENTER, 0, 0); + + lv_obj_t* img2 = lv_img_create(btn2); + lv_img_set_src(img2, resource_get_img("icon_volume_add")); + lv_obj_set_style_image_recolor_opa(img2, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_image_recolor(img2, lv_color_black(), LV_PART_MAIN); + lv_obj_align(img2, LV_ALIGN_CENTER, 0, 0); + + ctx->btn_volume_reduce = btn1; + ctx->btn_volume_add = btn2; +} + +static void obj_play_anim_ready_callback(lv_anim_t* a_p) +{ + page_ctx_t* ctx = lv_anim_get_user_data(a_p); + + if (a_p->act_time == 0) + return; + + lv_obj_t* img = (lv_obj_t*)a_p->var; + const char* img_src_next; + img_src_next = (lv_strcmp(lv_img_get_src(img), resource_get_img("icon_start")) == 0) ? resource_get_img("icon_pause") : resource_get_img("icon_start"); + lv_img_set_src(img, img_src_next); + + ctx->music_is_playing = (lv_strcmp(lv_img_get_src(img), resource_get_img("icon_pause")) == 0); + + lv_anim_t a; + lv_anim_init(&a); + lv_anim_set_var(&a, img); + lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_img_set_angle); + lv_anim_set_values(&a, 1800, 3600); + lv_anim_set_time(&a, 300); + + lv_anim_set_path_cb(&a, lv_anim_path_ease_out); + lv_anim_start(&a); +} + +static void obj_play_event_handler(lv_event_t* event) +{ + lv_event_code_t code = lv_event_get_code(event); + lv_obj_t* obj = lv_event_get_target(event); + page_ctx_t* ctx = lv_event_get_user_data(event); + + if (code == LV_EVENT_CLICKED) { + lv_obj_t* img = lv_obj_get_child(obj, 0); + + lv_anim_t a; + lv_anim_init(&a); + lv_anim_set_var(&a, img); + lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_img_set_angle); + lv_anim_set_ready_cb(&a, obj_play_anim_ready_callback); + lv_anim_set_user_data(&a, ctx); + lv_anim_set_values(&a, 0, 1800); + lv_anim_set_time(&a, 300); + lv_anim_set_path_cb(&a, lv_anim_path_ease_in); + lv_anim_start(&a); + } +} + +static void obj_play_create(lv_obj_t* par) +{ + page_ctx_t* ctx = lv_obj_get_user_data(par); + + lv_obj_t* obj = lv_obj_create(ctx->arc_play); + lv_obj_set_size(obj, 104, 104); + lv_obj_add_style(obj, &ctx->btn_style, LV_PART_MAIN); + lv_obj_add_style(obj, &ctx->trans_style, LV_STATE_DEFAULT); + lv_obj_add_style(obj, &ctx->trans_style, LV_STATE_PRESSED); + lv_obj_set_style_bg_color(obj, lv_color_white(), LV_STATE_PRESSED); + lv_obj_add_event(obj, obj_play_event_handler, LV_EVENT_ALL, ctx); + lv_obj_set_style_radius(obj, LV_RADIUS_CIRCLE, LV_PART_MAIN); + lv_obj_set_style_bg_color(obj, MC_COLOR_GRAY, LV_PART_MAIN); + lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN); + lv_obj_align(obj, LV_ALIGN_CENTER, 0, 0); + + lv_obj_t* img = lv_img_create(obj); + lv_img_set_src(img, resource_get_img("icon_start")); + lv_obj_align(img, LV_ALIGN_CENTER, 0, 0); + + ctx->obj_play = obj; +} + +static void spangroup_time_update(page_ctx_t* ctx, uint32_t cur_ms, uint32_t end_ms) +{ + uint32_t cur_min = cur_ms / 60 / 1000; + uint32_t cur_sec = (cur_ms / 1000) % 60; + + uint32_t end_min = end_ms / 60 / 1000; + uint32_t end_sec = (end_ms / 1000) % 60; + + char cur_buf[32]; + char end_buf[32]; + + lv_snprintf(cur_buf, sizeof(cur_buf), "%d:%02d", cur_min, cur_sec); + lv_snprintf(end_buf, sizeof(end_buf), " -- %d:%02d", end_min, end_sec); + lv_span_set_text(ctx->span_cur_time, cur_buf); + lv_span_set_text(ctx->span_end_time, end_buf); +} + +static void arc_play_event_handler(lv_event_t* event) +{ + page_ctx_t* ctx = lv_event_get_user_data(event); + int32_t value = lv_arc_get_value(ctx->arc_play); + ctx->music_current_time = lv_map(value, lv_arc_get_min_value(ctx->arc_play), lv_arc_get_max_value(ctx->arc_play), 0, ctx->music_end_time); + spangroup_time_update(ctx, ctx->music_current_time, ctx->music_end_time); +} + +static void arc_play_create(lv_obj_t* par) +{ + page_ctx_t* ctx = lv_obj_get_user_data(par); + + lv_obj_t* arc = lv_arc_create(par); + lv_obj_set_style_arc_color(arc, MC_COLOR_PINK, LV_PART_INDICATOR); + lv_obj_set_style_arc_width(arc, 6, LV_PART_INDICATOR); + + lv_obj_set_style_bg_color(arc, MC_COLOR_PINK, LV_PART_KNOB); // 设置 arc knob颜色 + lv_obj_set_style_pad_all(arc, 0, LV_PART_KNOB); // 设置arc knob size + + lv_obj_set_style_arc_color(arc, MC_COLOR_PINK_BG, LV_PART_MAIN); + lv_obj_set_style_arc_width(arc, 6, LV_PART_MAIN); + + lv_obj_set_style_border_width(arc, 0, LV_PART_MAIN); + lv_obj_set_size(arc, 121, 121); + lv_obj_align(arc, LV_ALIGN_CENTER, 0, 0); + lv_obj_add_event_cb(arc, arc_play_event_handler, LV_EVENT_VALUE_CHANGED, ctx); + + lv_arc_set_start_angle(arc, 270); + lv_arc_set_bg_angles(arc, 0, 360); + lv_arc_set_range(arc, 0, 1000); + lv_arc_set_rotation(arc, 270); + + ctx->arc_play = arc; +} + +static void spangroup_time_create(lv_obj_t* par) +{ + page_ctx_t* ctx = lv_obj_get_user_data(par); + + lv_obj_t* label = lv_label_create(par); + lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN); + lv_label_set_text(label, "--"); + lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 87); + + lv_obj_t* spangroup = lv_spangroup_create(par); + lv_obj_set_style_text_font(spangroup, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN); + + lv_span_t* cur_span = lv_spangroup_new_span(spangroup); + lv_style_set_text_color(&cur_span->style, lv_color_hex(0xB9005E)); + + lv_span_t* end_span = lv_spangroup_new_span(spangroup); + lv_style_set_text_color(&end_span->style, lv_color_hex(0x666666)); + lv_span_set_text(end_span, "--"); + + lv_obj_align(spangroup, LV_ALIGN_TOP_MID, 0, 87); + + ctx->span_cur_time = cur_span; + ctx->span_end_time = end_span; +} + +static void label_music_info_create(lv_obj_t* par) +{ + page_ctx_t* ctx = lv_obj_get_user_data(par); + + lv_obj_t* label = lv_label_create(par); + lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN); + lv_obj_set_style_text_color(label, lv_color_white(), LV_PART_MAIN); + lv_label_set_text(label, ""); + lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_align(label, LV_ALIGN_CENTER); + lv_obj_set_width(label, 166); + lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 259); + + ctx->label_music_info = label; +} + +static void music_change_current(page_ctx_t* ctx, int index) +{ + index %= MUSIC_INFO_CNT; + + ctx->music_current_time = 0; + ctx->music_end_time = ctx->music_info_grp[index].time_ms; + + lv_label_set_text(ctx->label_music_info, ctx->music_info_grp[index].name); + lv_arc_set_value(ctx->arc_play, ctx->music_current_time * 1000 / ctx->music_end_time); + spangroup_time_update(ctx, ctx->music_current_time, ctx->music_end_time); +} + +static void music_change_next(page_ctx_t* ctx, bool next) +{ + if (next) { + if (ctx->music_current_index >= MUSIC_INFO_CNT - 1) { + ctx->music_current_index = 0; + } else { + ctx->music_current_index++; + } + } else { + if (ctx->music_current_index == 0) { + ctx->music_current_index = MUSIC_INFO_CNT; + } else { + ctx->music_current_index--; + } + } + + music_change_current(ctx, ctx->music_current_index); +} + +static void btn_music_event_handler(lv_event_t* event) +{ + lv_obj_t* obj = lv_event_get_target(event); + lv_event_code_t code = lv_event_get_code(event); + page_ctx_t* ctx = lv_event_get_user_data(event); + + if (code == LV_EVENT_CLICKED) { + lv_obj_t* img = lv_obj_get_child(obj, 0); + + bool is_next = (lv_strcmp(lv_img_get_src(img), resource_get_img("icon_next")) == 0); + music_change_next(ctx, is_next); + } +} + +static void btn_music_ctrl_create(lv_obj_t* par) +{ + page_ctx_t* ctx = lv_obj_get_user_data(par); + + lv_obj_t* btn1 = lv_btn_create(par); + lv_obj_set_size(btn1, 85, 64); + lv_obj_add_style(btn1, &ctx->btn_style, LV_PART_MAIN); + lv_obj_set_style_bg_color(btn1, lv_color_white(), LV_STATE_PRESSED); + lv_obj_add_style(btn1, &ctx->trans_style, LV_STATE_DEFAULT); + lv_obj_add_style(btn1, &ctx->trans_style, LV_STATE_PRESSED); + lv_obj_align(btn1, LV_ALIGN_BOTTOM_MID, -lv_obj_get_style_width(btn1, 0) / 2 - 3, -10); + lv_obj_add_event(btn1, btn_music_event_handler, LV_EVENT_ALL, ctx); + + lv_obj_t* btn2 = lv_btn_create(par); + lv_obj_set_size(btn2, 85, 64); + lv_obj_add_style(btn2, &ctx->btn_style, LV_PART_MAIN); + lv_obj_set_style_bg_color(btn2, lv_color_white(), LV_STATE_PRESSED); + lv_obj_add_style(btn2, &ctx->trans_style, LV_STATE_DEFAULT); + lv_obj_add_style(btn2, &ctx->trans_style, LV_STATE_PRESSED); + lv_obj_add_event(btn2, btn_music_event_handler, LV_EVENT_ALL, ctx); + lv_obj_align(btn2, LV_ALIGN_BOTTOM_MID, lv_obj_get_style_width(btn2, 0) / 2 + 3, -10); + + lv_obj_t* img1 = lv_img_create(btn1); + lv_img_set_src(img1, resource_get_img("icon_prev")); + lv_obj_align(img1, LV_ALIGN_CENTER, 0, 0); + + lv_obj_t* img2 = lv_img_create(btn2); + lv_img_set_src(img2, resource_get_img("icon_next")); + lv_obj_align(img2, LV_ALIGN_CENTER, 0, 0); + + ctx->btn_music_ctrl_prev = btn1; + ctx->btn_music_ctrl_next = btn2; +} + +static void music_update(lv_timer_t* timer) +{ + page_ctx_t* ctx = lv_timer_get_user_data(timer); + + if (!ctx->music_is_playing) { + return; + } + ctx->music_current_time += timer->period; + + if (ctx->music_current_time > ctx->music_end_time) { + music_change_next(ctx, true); + } + + lv_arc_set_value(ctx->arc_play, ctx->music_current_time * 1000 / ctx->music_end_time); + spangroup_time_update(ctx, ctx->music_current_time, ctx->music_end_time); +} + +static void auto_event_create(page_ctx_t* ctx) +{ + lv_auto_event_data_t ae_grp[EVENT_CNT] = { + { &ctx->obj_play, LV_EVENT_CLICKED, 1000 }, + { &ctx->btn_volume_add, LV_EVENT_CLICKED, 500 }, + { &ctx->btn_volume_add, LV_EVENT_CLICKED, 500 }, + { &ctx->btn_volume_reduce, LV_EVENT_CLICKED, 500 }, + { &ctx->btn_volume_reduce, LV_EVENT_CLICKED, 500 }, + { &ctx->btn_music_ctrl_next, LV_EVENT_CLICKED, 2000 }, + { &ctx->btn_music_ctrl_next, LV_EVENT_CLICKED, 2000 }, + { &ctx->obj_play, LV_EVENT_CLICKED, 1000 }, + { &ctx->base.obj, LV_EVENT_LEAVE, 1000 }, + }; + for (int i = 0; i < EVENT_CNT; i++) { + ctx->event_data[i] = ae_grp[i]; + } + AUTO_EVENT_CREATE(ctx->auto_event, ctx->event_data); +} + +static void on_page_construct(lv_fragment_t* self, void* args) +{ + LV_LOG_INFO("self: %p args: %p", self, args); + + page_ctx_t* ctx = (page_ctx_t*)self; + + lv_style_init(&ctx->btn_style); + lv_style_set_bg_color(&ctx->btn_style, MC_COLOR_GRAY); + lv_style_set_border_width(&ctx->btn_style, 0); + lv_style_set_outline_width(&ctx->btn_style, 0); + lv_style_set_radius(&ctx->btn_style, 10); + + lv_style_init(&ctx->trans_style); + static lv_style_transition_dsc_t dsc; + static const lv_style_prop_t props[] = { + LV_STYLE_BG_COLOR, + LV_STYLE_TRANSFORM_WIDTH, + LV_STYLE_TRANSFORM_HEIGHT, + LV_STYLE_PROP_INV + }; + lv_style_transition_dsc_init( + &dsc, + props, + lv_anim_path_ease_in_out, + 50, + 0, + NULL); + lv_style_set_transition(&ctx->trans_style, &dsc); + + music_info_t music_info_grp[MUSIC_INFO_CNT] = { + { .name = "Wannabe (Instrumental)", MUSIC_TIME_TO_MS(3, 37) }, + { .name = "River", MUSIC_TIME_TO_MS(3, 40) }, + { .name = "I Feel Tired", MUSIC_TIME_TO_MS(3, 25) }, + }; + + for (int i = 0; i < MUSIC_INFO_CNT; i++) { + ctx->music_info_grp[i] = music_info_grp[i]; + } + + ctx->music_current_time = 0; + ctx->music_end_time = 0; + ctx->music_current_index = 0; + ctx->music_is_playing = false; +} + +static void on_page_destruct(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); + + page_ctx_t* ctx = (page_ctx_t*)self; + lv_style_reset(&ctx->btn_style); + lv_style_reset(&ctx->trans_style); +} + +static void on_page_attached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static void on_page_detached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container) +{ + LV_LOG_INFO("self: %p container: %p", self, container); + + lv_obj_t* root = lv_obj_create(container); + lv_obj_remove_style_all(root); + lv_obj_add_style(root, resource_get_style("root_def"), 0); + lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL); + lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE); + lv_obj_set_user_data(root, self); + return root; +} + +static void on_page_created(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + + page_ctx_t* ctx = (page_ctx_t*)self; + + btn_volume_create(obj); + bar_volume_create(obj); + bar_volume_set_value(50, ctx->bar_volume); + + arc_play_create(obj); + obj_play_create(obj); + spangroup_time_create(obj); + label_music_info_create(obj); + btn_music_ctrl_create(obj); + + ctx->music_is_playing = false; + music_change_current(ctx, ctx->music_current_index); + auto_event_create(ctx); + ctx->timer_music_update = lv_timer_create(music_update, 100, ctx); +} + +static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + + page_ctx_t* ctx = (page_ctx_t*)self; + lv_timer_del(ctx->timer_music_update); + AUTO_EVENT_DELETE(ctx->auto_event); +} + +static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); +} + +static bool on_page_event(lv_fragment_t* self, int code, void* user_data) +{ + LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data); + return false; +} + +PAGE_CLASS_DEF(music); diff --git a/bandx/page/page.c b/bandx/page/page.c new file mode 100644 index 0000000000000000000000000000000000000000..5dd159d6df182b4ddcc7263cdff0d2b09a613b5e --- /dev/null +++ b/bandx/page/page.c @@ -0,0 +1,113 @@ +/** + * @file page.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "page.h" + +#include "lvgl/lvgl.h" + +/********************* + * DEFINES + *********************/ + +#define PAGE_STYLE_MATCH(NAME) \ + do { \ + if (lv_strcmp(name, #NAME) == 0) { \ + return &g_style_grp.NAME; \ + } \ + } while (0) + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static bool autoshow_enable = false; +static bool is_last_page_dialplate = false; + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void page_init(void) +{ +} + +lv_fragment_t* page_create(const char* name, void* arg) +{ + LV_LOG_USER("name: %s arg: %p", name, arg); + +#define PAGE_DEF(NAME) \ + do { \ + if (lv_strcmp(name, #NAME) == 0) { \ + extern const lv_fragment_class_t _page_##NAME##_cls; \ + return lv_fragment_create(&_page_##NAME##_cls, arg); \ + } \ + } while (0); +#include "page.inc" +#undef PAGE_DEF + + LV_LOG_WARN("NO match for %s", name); + return NULL; +} + +bool page_push(lv_fragment_t* self, const char* name, void* arg) +{ + lv_fragment_t* page = page_create(name, NULL); + + if (!page) { + LV_LOG_WARN("page push %s failed", name); + return false; + } + + lv_fragment_manager_t* manager = lv_fragment_get_manager(self); + lv_obj_t* const* container = lv_fragment_get_container(self); + lv_fragment_manager_push(manager, page, container); + return true; +} + +void page_pop(lv_fragment_t* self) +{ + lv_fragment_manager_t* manager = lv_fragment_get_manager(self); + lv_fragment_manager_pop(manager); +} + +bool page_get_autoshow_enable(void) +{ + return autoshow_enable; +} + +void page_set_autoshow_enable(bool en) +{ + autoshow_enable = en; +} + +void page_set_last_page_dialplate(bool value) +{ + is_last_page_dialplate = value; +} + +bool page_is_last_page_dialplate(void) +{ + return is_last_page_dialplate; +} + +/********************** + * STATIC FUNCTIONS + **********************/ diff --git a/bandx/page/page.h b/bandx/page/page.h new file mode 100644 index 0000000000000000000000000000000000000000..e73af20fc362909b1fec940436dd4ad59426e32e --- /dev/null +++ b/bandx/page/page.h @@ -0,0 +1,139 @@ +/** + * @file page.h + * + */ + +#ifndef PAGE_H +#define PAGE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include <lvgl/lvgl.h> + +#ifdef CONFIG_LVX_USE_DEMO_BANDX + +#include "../resource/resource.h" +#include "../utils/lv_auto_event.h" + +/********************* + * DEFINES + *********************/ + +#define PAGE_CLASS_DEF(NAME) \ + const lv_fragment_class_t _page_##NAME##_cls = { \ + .constructor_cb = on_page_construct, \ + .destructor_cb = on_page_destruct, \ + .attached_cb = on_page_attached, \ + .detached_cb = on_page_detached, \ + .create_obj_cb = on_page_create, \ + .obj_created_cb = on_page_created, \ + .obj_will_delete_cb = on_page_will_delete, \ + .obj_deleted_cb = on_page_deleted, \ + .event_cb = on_page_event, \ + .instance_size = sizeof(page_ctx_t) \ + } + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Initialize the page manager. + */ +void page_init(void); + +/** + * Create a new page fragment. + * @param name The name of the page. + * @param arg Arguments to be passed for the page fragment create. + * @return A pointer to the newly created page fragment. + */ +lv_fragment_t* page_create(const char* name, void* arg); + +/** + * Push a new page onto the stack. + * @param self Pointer to the current page fragment. + * @param name The name of the page to push. + * @param arg Arguments for the new page. + * @return Return true if the push operation was successful. + */ +bool page_push(lv_fragment_t* self, const char* name, void* arg); + +/** + * Pop the current page from the stack. + * @param self Pointer to the current page fragment. + */ +void page_pop(lv_fragment_t* self); + +/** + * Get the auto-show enable status. + * @return Return true if auto-show is enabled, false otherwise. + */ +bool page_get_autoshow_enable(void); + +/** + * Set the auto-show enable status. + * @param en Enable or disable the auto-show. + */ +void page_set_autoshow_enable(bool en); + +/** + * Set the status of the last page dial plate. + * @param value Set to true to indicate that the last page is dial plate, false otherwise. + */ +void page_set_last_page_dialplate(bool value); + +/** + * Check if the last page is dial plate. + * @return Return true if the last page is dial plate, false otherwise. + */ +bool page_is_last_page_dialplate(void); + +/********************** + * MACROS + **********************/ +#define PAGE_VER_RES 368 +#define PAGE_HOR_RES 194 + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) +#endif + +#if !defined(BANDX_REGULAR_FONT) && !defined(BANDX_BOLD_FONT) +#define BANDX_REGULAR_FONT "AlibabaPuHuiTi-3-55-Regular" +#define BANDX_BOLD_FONT "AlibabaPuHuiTi-3-95-ExtraBold" +#endif + +#define AUTO_EVENT_CREATE(ae, ae_data) \ + do { \ + if (page_get_autoshow_enable()) { \ + ae = lv_auto_event_create(ae_data, ARRAY_SIZE(ae_data)); \ + } else { \ + ae = NULL; \ + } \ + } while (0) + +#define AUTO_EVENT_DELETE(ae) \ + do { \ + if (page_get_autoshow_enable()) { \ + lv_auto_event_del(ae); \ + ae = NULL; \ + } \ + } while (0) + +#endif /*CONFIG_LVX_USE_DEMO_BANDX*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*PAGE_H*/ diff --git a/bandx/page/page.inc b/bandx/page/page.inc new file mode 100644 index 0000000000000000000000000000000000000000..2018b5503f58cf4f076c4f7a471d38d0ecb0e82c --- /dev/null +++ b/bandx/page/page.inc @@ -0,0 +1,11 @@ +/* page list */ +PAGE_DEF(template) +PAGE_DEF(dialplate) +PAGE_DEF(launcher) +PAGE_DEF(flashlight) +PAGE_DEF(settings) +PAGE_DEF(stop_watch) +PAGE_DEF(sleep) +PAGE_DEF(sport) +PAGE_DEF(music) +PAGE_DEF(heart_rate) \ No newline at end of file diff --git a/bandx/page/settings.c b/bandx/page/settings.c new file mode 100644 index 0000000000000000000000000000000000000000..bb03f1d8a2ee8f5a7610de76c01de42d747149d0 --- /dev/null +++ b/bandx/page/settings.c @@ -0,0 +1,286 @@ +/** + * @file settings.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "page.h" + +#include "../utils/lv_obj_ext_func.h" + +/********************* + * DEFINES + *********************/ + +#define BAR_SETTING_CNT 2 +#define EVENT_CNT 9 + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + const char* text; + const char* img_src_left; + const char* img_src_right; + lv_coord_t y; + + lv_obj_t* img_left; + lv_obj_t* img_right; + lv_obj_t* bar; +} bar_setting_t; + +typedef struct { + lv_fragment_t base; + bar_setting_t bar_setting_grp[BAR_SETTING_CNT]; + lv_auto_event_data_t event_data[EVENT_CNT]; + lv_auto_event_t* auto_event; +} page_ctx_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +#define BX_NAME "BandX" +#define BX_VERSION "v1.0" + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void bar_setting_event_handler(lv_event_t* event) +{ + lv_obj_t* obj = lv_event_get_user_data(event); + lv_event_code_t code = lv_event_get_code(event); + if (code == LV_EVENT_CLICKED) { + bar_setting_t* bar_setting = (bar_setting_t*)lv_obj_get_user_data(obj); + const char* img_src = lv_img_get_src(obj); + int add_value = (lv_strcmp(img_src, resource_get_img(bar_setting->img_src_left)) == 0) ? -10 : +10; + int16_t old_value = lv_bar_get_value(bar_setting->bar); + lv_bar_set_value(bar_setting->bar, old_value + add_value, LV_ANIM_ON); + } +} + +static void img_setting_set_style(lv_obj_t* img) +{ + lv_obj_add_event(img, bar_setting_event_handler, LV_EVENT_ALL, img); + lv_obj_add_flag(img, LV_OBJ_FLAG_CLICKABLE); + + lv_obj_set_style_image_recolor_opa(img, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_image_recolor(img, lv_color_make(0x66, 0x66, 0x66), LV_PART_MAIN); + lv_obj_set_style_image_recolor(img, lv_color_make(0x00, 0x89, 0xFF), LV_STATE_PRESSED); +} + +static void bar_setting_create(lv_obj_t* par, bar_setting_t* bar_setting, int len) +{ + for (int i = 0; i < len; i++) { + lv_obj_t* obj_base = lv_obj_create(par); + lv_obj_remove_style_all(obj_base); + lv_obj_set_size(obj_base, PAGE_HOR_RES - 40, 50); + lv_obj_clear_flag(obj_base, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_align(obj_base, LV_ALIGN_TOP_MID, 0, bar_setting[i].y); + + lv_obj_t* label = lv_label_create(obj_base); + lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN); + lv_obj_set_style_text_color(label, lv_color_make(0x66, 0x66, 0x66), LV_PART_MAIN); + lv_label_set_text(label, bar_setting[i].text); + lv_obj_align_to(label, obj_base, LV_ALIGN_TOP_LEFT, 0, 0); + + lv_obj_t* bar = lv_bar_create(obj_base); + lv_obj_set_size(bar, 74, 6); + lv_obj_set_style_bg_color(bar, lv_color_make(0x00, 0x89, 0xFF), LV_PART_INDICATOR); + lv_obj_set_style_bg_color(bar, lv_color_make(0x33, 0x33, 0x33), LV_PART_MAIN); + lv_obj_align(bar, LV_ALIGN_BOTTOM_MID, 0, -10); + lv_bar_set_value(bar, 50, LV_ANIM_OFF); + bar_setting[i].bar = bar; + + lv_obj_t* img1 = lv_img_create(obj_base); + lv_img_set_src(img1, resource_get_img(bar_setting[i].img_src_left)); + lv_obj_set_user_data(img1, &(bar_setting[i])); + img_setting_set_style(img1); + lv_obj_align_to(img1, bar, LV_ALIGN_OUT_LEFT_MID, -10, 0); + + lv_obj_t* img2 = lv_img_create(obj_base); + lv_img_set_src(img2, resource_get_img(bar_setting[i].img_src_right)); + lv_obj_set_user_data(img2, &(bar_setting[i])); + img_setting_set_style(img2); + lv_obj_align_to(img2, bar, LV_ALIGN_OUT_RIGHT_MID, 10, 0); + + bar_setting[i].img_left = img1; + bar_setting[i].img_right = img2; + } +} + +static void sw_auto_show_event_handler(lv_event_t* e) +{ + lv_event_code_t code = lv_event_get_code(e); + page_ctx_t* ctx = lv_event_get_user_data(e); + + if (code == LV_EVENT_VALUE_CHANGED) { + page_set_autoshow_enable(!page_get_autoshow_enable()); + if (page_get_autoshow_enable()) { + LV_LOG_USER("Auto-show Begin ..."); + lv_obj_send_event(ctx->base.obj, LV_EVENT_LEAVE, NULL); + } else { + lv_auto_event_del(ctx->auto_event); + ctx->auto_event = NULL; + LV_LOG_USER("Auto-show End ..."); + } + } +} + +static void sw_auto_show_create(page_ctx_t* ctx) +{ + lv_obj_t* par = ctx->base.obj; + lv_obj_t* label = lv_label_create(par); + lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN); + lv_obj_set_style_text_color(label, lv_color_make(0x66, 0x66, 0x66), LV_PART_MAIN); + lv_label_set_text(label, "Auto-show"); + lv_obj_align(label, LV_ALIGN_TOP_LEFT, 23, 187); + + lv_obj_t* sw = lv_switch_create(par); + lv_obj_set_size(sw, 41, 23); + lv_obj_align_to(sw, label, LV_ALIGN_OUT_RIGHT_MID, 12, -5); + lv_obj_set_style_bg_color(sw, lv_color_make(0x33, 0x33, 0x33), LV_PART_MAIN); + lv_obj_set_style_bg_color(sw, lv_color_make(0x00, 0x89, 0xFF), LV_PART_INDICATOR); + lv_obj_set_style_bg_color(sw, lv_color_make(0xAA, 0xAA, 0xAA), LV_PART_KNOB); + lv_obj_set_style_outline_width(sw, 0, LV_PART_MAIN); + lv_obj_add_event(sw, sw_auto_show_event_handler, LV_EVENT_ALL, ctx); + page_get_autoshow_enable() ? lv_obj_add_state(sw, LV_STATE_CHECKED) : lv_obj_clear_state(sw, LV_STATE_CHECKED); +} + +static void label_info_create(lv_obj_t* par) +{ + lv_obj_t* label = lv_label_create(par); + lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN); + lv_obj_set_style_text_color(label, lv_color_white(), LV_PART_MAIN); + lv_label_set_text(label, BX_NAME " " BX_VERSION "\n"__DATE__ + "\nBuild"); + lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 270); +} + +static void auto_event_create(page_ctx_t* ctx) +{ + bar_setting_t* bar_setting_grp = ctx->bar_setting_grp; + lv_auto_event_data_t ae_grp[EVENT_CNT] = { + { &(bar_setting_grp[0].img_left), LV_EVENT_CLICKED, 500 }, + { &(bar_setting_grp[0].img_left), LV_EVENT_CLICKED, 500 }, + { &(bar_setting_grp[0].img_right), LV_EVENT_CLICKED, 500 }, + { &(bar_setting_grp[0].img_right), LV_EVENT_CLICKED, 500 }, + { &(bar_setting_grp[1].img_left), LV_EVENT_CLICKED, 500 }, + { &(bar_setting_grp[1].img_left), LV_EVENT_CLICKED, 500 }, + { &(bar_setting_grp[1].img_right), LV_EVENT_CLICKED, 500 }, + { &(bar_setting_grp[1].img_right), LV_EVENT_CLICKED, 500 }, + { &ctx->base.obj, LV_EVENT_LEAVE, 1000 }, + }; + for (int i = 0; i < EVENT_CNT; i++) { + ctx->event_data[i] = ae_grp[i]; + } + AUTO_EVENT_CREATE(ctx->auto_event, ctx->event_data); +} + +static void on_root_event(lv_event_t* e) +{ + lv_obj_t* root = lv_event_get_current_target_obj(e); + lv_event_code_t code = lv_event_get_code(e); + page_ctx_t* ctx = lv_obj_get_user_data(root); + + if (code == LV_EVENT_GESTURE) { + lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act()); + if (dir == LV_DIR_RIGHT) { + lv_obj_send_event(root, LV_EVENT_LEAVE, NULL); + } + } else if (code == LV_EVENT_LEAVE) { + page_pop(&ctx->base); + } +} + +static void on_page_construct(lv_fragment_t* self, void* args) +{ + LV_LOG_INFO("self: %p args: %p", self, args); + + page_ctx_t* ctx = (page_ctx_t*)self; + + bar_setting_t bar_setting_grp[BAR_SETTING_CNT] = { + { .text = "Volume", .img_src_left = "icon_volume_reduce", .img_src_right = "icon_volume_add", 24 }, + { .text = "Backlight", .img_src_left = "icon_minus", .img_src_right = "icon_plus", 98 }, + }; + + for (int i = 0; i < BAR_SETTING_CNT; i++) { + ctx->bar_setting_grp[i] = bar_setting_grp[i]; + } +} + +static void on_page_destruct(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static void on_page_attached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static void on_page_detached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container) +{ + LV_LOG_INFO("self: %p container: %p", self, container); + + lv_obj_t* root = lv_obj_create(container); + lv_obj_remove_style_all(root); + lv_obj_add_style(root, resource_get_style("root_def"), 0); + lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL); + lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE); + lv_obj_set_user_data(root, self); + return root; +} + +static void on_page_created(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + + page_ctx_t* ctx = (page_ctx_t*)self; + + bar_setting_create(obj, ctx->bar_setting_grp, BAR_SETTING_CNT); + sw_auto_show_create(ctx); + label_info_create(obj); + auto_event_create(ctx); +} + +static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + page_ctx_t* ctx = (page_ctx_t*)self; + lv_auto_event_del(ctx->auto_event); +} + +static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); +} + +static bool on_page_event(lv_fragment_t* self, int code, void* user_data) +{ + LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data); + return false; +} + +PAGE_CLASS_DEF(settings); diff --git a/bandx/page/sleep.c b/bandx/page/sleep.c new file mode 100644 index 0000000000000000000000000000000000000000..d20444f00d868c0d7dcc8b18c090fb0443757904 --- /dev/null +++ b/bandx/page/sleep.c @@ -0,0 +1,469 @@ +/** + * @file sleep.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "page.h" + +#include "../utils/lv_obj_ext_func.h" + +/********************* + * DEFINES + *********************/ + +#define SLEEP_TIME_CNT 6 +#define SLEEP_INFO_CNT 4 +#define EVENT_CNT 3 + +/********************** + * TYPEDEFS + **********************/ + +typedef enum { + SLEEP_TYPE_SHALLOW, + SLEEP_TYPE_DEEP, + SLEEP_TYPE_REM, + SLEEP_TYPE_AWAKE, + SLEEP_TYPE_MAX +} sleep_type; + +typedef struct { + const char* text; + sleep_type type; +} sleep_info_t; + +typedef struct { + sleep_type type; + uint32_t start_min; + uint32_t end_min; +} sleep_time_t; + +typedef struct { + lv_fragment_t base; + lv_obj_t* img_down; + lv_obj_t* img_up; + lv_obj_t* cont_sleep; + + uint32_t sleep_type_color[SLEEP_TYPE_MAX]; + sleep_time_t sleep_time_grp[SLEEP_TIME_CNT]; + sleep_info_t sleep_info_grp[SLEEP_INFO_CNT]; + lv_auto_event_data_t event_data[EVENT_CNT]; + lv_auto_event_t* auto_event; +} page_ctx_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +#define CONT_SLEEP_MOVE_DOWN(down, obj) \ + LV_OBJ_ADD_ANIM( \ + obj, \ + y, \ + (down) ? -PAGE_VER_RES : 0, LV_ANIM_TIME_DEFAULT) + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void cont_sleep_create(lv_obj_t* par) +{ + page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par); + lv_obj_t* obj = lv_obj_create(par); + lv_obj_remove_style_all(obj); + lv_obj_set_size(obj, PAGE_HOR_RES, PAGE_VER_RES * 2); + lv_obj_align(obj, LV_ALIGN_TOP_MID, 0, 0); + lv_obj_set_style_bg_color(obj, lv_color_black(), LV_PART_MAIN); + lv_obj_set_user_data(obj, ctx); + + ctx->cont_sleep = obj; +} + +static void img_arrow_event_handler(lv_event_t* event) +{ + lv_event_code_t code = lv_event_get_code(event); + lv_obj_t* obj = lv_event_get_current_target_obj(event); + page_ctx_t* ctx = (page_ctx_t*)lv_event_get_user_data(event); + + if (code == LV_EVENT_CLICKED) { + bool is_down = (lv_strcmp(lv_img_get_src(obj), resource_get_img("arrow_down")) == 0); + CONT_SLEEP_MOVE_DOWN(is_down, ctx->cont_sleep); + } +} + +static void img_arrow_create(lv_obj_t* par) +{ + page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par); + + lv_obj_t* img1 = lv_img_create(par); + lv_img_set_src(img1, resource_get_img("arrow_down")); + lv_obj_align(img1, LV_ALIGN_TOP_MID, 0, 339); + lv_obj_add_event(img1, img_arrow_event_handler, LV_EVENT_ALL, ctx); + lv_obj_add_flag(img1, LV_OBJ_FLAG_CLICKABLE); + + lv_obj_t* img2 = lv_img_create(par); + lv_img_set_src(img2, resource_get_img("arrow_up")); + lv_obj_align(img2, LV_ALIGN_TOP_MID, 0, PAGE_VER_RES + 12); + lv_obj_add_event(img2, img_arrow_event_handler, LV_EVENT_ALL, ctx); + lv_obj_add_flag(img2, LV_OBJ_FLAG_CLICKABLE); + + ctx->img_down = img1; + ctx->img_up = img2; +} + +static void img_sleep_create(lv_obj_t* par) +{ + lv_obj_t* img = lv_img_create(par); + lv_img_set_src(img, resource_get_img("sleep")); + lv_obj_align(img, LV_ALIGN_TOP_MID, 0, 32); +} + +static void label_score_create(lv_obj_t* par) +{ + lv_obj_t* label1 = lv_label_create(par); + lv_obj_set_style_text_font( + label1, + resource_get_font(BANDX_REGULAR_FONT "_20"), + LV_PART_MAIN); + lv_obj_set_style_text_color(label1, lv_color_hex(0x808080), LV_PART_MAIN); + lv_label_set_text(label1, "Score"); + lv_obj_align(label1, LV_ALIGN_TOP_MID, 0, 123); + + lv_obj_t* label2 = lv_label_create(par); + lv_obj_set_style_text_font( + label2, + resource_get_font(BANDX_REGULAR_FONT "_72"), + LV_PART_MAIN); + lv_obj_set_style_text_color(label2, lv_color_white(), LV_PART_MAIN); + lv_label_set_text(label2, "78"); + lv_obj_align(label2, LV_ALIGN_TOP_MID, 0, 148); +} + +static void label_total_time_create(lv_obj_t* par) +{ + lv_obj_t* label1 = lv_label_create(par); + lv_obj_set_style_text_font(label1, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN); + lv_obj_set_style_text_color(label1, lv_color_hex(0x808080), LV_PART_MAIN); + lv_label_set_text(label1, "Total time"); + lv_obj_align(label1, LV_ALIGN_TOP_MID, 0, 224); + + lv_obj_t* label2 = lv_label_create(par); + lv_obj_set_style_text_font(label2, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN); + lv_obj_set_style_text_color(label2, lv_color_hex(0x3FA9F5), LV_PART_MAIN); + lv_label_set_text(label2, "8 h 52 min"); + lv_obj_set_style_text_align(label2, LV_ALIGN_CENTER, 0); + lv_obj_align(label2, LV_ALIGN_TOP_MID, 0, 249); + + lv_obj_t* spangroup = lv_spangroup_create(par); + lv_obj_set_style_text_font(spangroup, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN); + + lv_span_t* span = lv_spangroup_new_span(spangroup); + lv_span_set_text_static(span, "00:29"); + lv_style_set_text_color(&span->style, lv_color_hex(0x3FA9F5)); + + lv_span_t* span1 = lv_spangroup_new_span(spangroup); + lv_span_set_text_static(span1, "-"); + lv_style_set_text_color(&span1->style, lv_color_hex(0x808080)); + + lv_span_t* span2 = lv_spangroup_new_span(spangroup); + lv_span_set_text_static(span2, "09:48"); + lv_style_set_text_color(&span2->style, lv_color_hex(0x3FA9F5)); + lv_obj_align(spangroup, LV_ALIGN_TOP_MID, 0, 274); +} + +static uint32_t sleep_time_get_sum(sleep_time_t* sleep_time, int len, sleep_type check_type) +{ + uint32_t sum = 0; + if (check_type == SLEEP_TYPE_SHALLOW) { + sum = sleep_time[0].end_min - sleep_time[0].start_min; + sum -= sleep_time_get_sum(sleep_time, len, SLEEP_TYPE_DEEP); + sum -= sleep_time_get_sum(sleep_time, len, SLEEP_TYPE_REM); + sum -= sleep_time_get_sum(sleep_time, len, SLEEP_TYPE_AWAKE); + } else { + for (int i = 0; i < len; i++) { + if (sleep_time[i].type == check_type) { + uint32_t time = sleep_time[i].end_min - sleep_time[i].start_min; + sum += time; + } + } + } + return sum; +} + +static void obj_sleep_time_create(lv_obj_t* par, sleep_time_t* sleep_time, int len) +{ + page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par); + + lv_obj_t* obj_base = lv_obj_create(par); + lv_obj_remove_style_all(obj_base); + lv_obj_set_size(obj_base, 165, 74); + lv_obj_set_style_radius(obj_base, 10, LV_PART_MAIN); + lv_obj_set_style_clip_corner(obj_base, 10, LV_PART_MAIN); + + lv_obj_align(obj_base, LV_ALIGN_TOP_MID, 0, PAGE_VER_RES + 42); + lv_obj_clear_flag(obj_base, LV_OBJ_FLAG_SCROLLABLE); + + uint32_t time_len = sleep_time[0].end_min - sleep_time[0].start_min; + uint32_t time_start = sleep_time[0].start_min; + lv_coord_t width_base = lv_obj_get_style_width(obj_base, 0); + + for (int i = 0; i < len; i++) { + uint8_t type_index = sleep_time[i].type; + lv_color_t color = lv_color_hex(ctx->sleep_type_color[type_index]); + + lv_obj_t* obj = lv_obj_create(obj_base); + lv_obj_set_style_radius(obj, 0, LV_PART_MAIN); + lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN); + lv_obj_set_style_bg_color(obj, color, LV_PART_MAIN); + + lv_coord_t width = width_base * (sleep_time[i].end_min - sleep_time[i].start_min) / time_len; + lv_coord_t x_ofs = width_base * (sleep_time[i].start_min - time_start) / time_len; + + lv_obj_set_size(obj, width, lv_obj_get_style_height(obj_base, 0)); + lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_align(obj, LV_ALIGN_TOP_LEFT, x_ofs, 0); + } + + lv_obj_t* label1 = lv_label_create(par); + lv_obj_set_style_text_font( + label1, + resource_get_font(BANDX_REGULAR_FONT "_15"), + LV_PART_MAIN); + lv_obj_set_style_text_color( + label1, + lv_color_make(0x80, 0x80, 0x80), + LV_PART_MAIN); + + uint32_t start_min = sleep_time[0].start_min; + lv_label_set_text_fmt(label1, "%02" LV_PRIu32 ":%02" LV_PRIu32, start_min / 60, start_min % 60); + lv_obj_align_to(label1, obj_base, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 5); + + lv_obj_t* label2 = lv_label_create(par); + lv_obj_set_style_text_font( + label2, + resource_get_font(BANDX_REGULAR_FONT "_15"), + LV_PART_MAIN); + lv_obj_set_style_text_color( + label2, + lv_color_make(0x80, 0x80, 0x80), + LV_PART_MAIN); + + uint32_t end_min = sleep_time[0].end_min; + lv_label_set_text_fmt(label2, "%02" LV_PRIu32 ":%02" LV_PRIu32, end_min / 60, end_min % 60); + lv_obj_align_to(label2, obj_base, LV_ALIGN_OUT_BOTTOM_RIGHT, 0, 5); +} + +static void obj_sleep_info_create(lv_obj_t* par, sleep_info_t* sleep_info, int len) +{ + page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par); + + for (int i = 0; i < len; i++) { + lv_obj_t* obj_base = lv_obj_create(par); + lv_obj_remove_style_all(obj_base); + lv_obj_set_style_bg_opa(obj_base, LV_OPA_0, LV_PART_MAIN); + lv_obj_set_size(obj_base, PAGE_HOR_RES - 40, 50); + lv_obj_clear_flag(obj_base, LV_OBJ_FLAG_SCROLLABLE); + + lv_coord_t y_ofs = PAGE_VER_RES + 150 + (lv_obj_get_style_height(obj_base, 0) + 5) * i; + lv_obj_align(obj_base, LV_ALIGN_TOP_MID, 0, y_ofs); + + lv_obj_t* label1 = lv_label_create(obj_base); + lv_obj_set_style_text_font( + label1, + resource_get_font(BANDX_REGULAR_FONT "_20"), + LV_PART_MAIN); + lv_obj_set_style_text_color( + label1, + lv_color_make(0x80, 0x80, 0x80), + LV_PART_MAIN); + lv_label_set_text(label1, sleep_info[i].text); + lv_obj_align_to(label1, obj_base, LV_ALIGN_TOP_LEFT, 0, 0); + + lv_obj_t* label2 = lv_label_create(obj_base); + lv_obj_set_style_text_font( + label2, + resource_get_font(BANDX_REGULAR_FONT "_20"), + LV_PART_MAIN); + lv_obj_set_style_text_color( + label2, + lv_color_white(), + LV_PART_MAIN); + uint32_t time = sleep_time_get_sum( + ctx->sleep_time_grp, + SLEEP_TIME_CNT, + sleep_info[i].type); + lv_label_set_text_fmt(label2, "%" LV_PRIu32 "h %" LV_PRIu32 "min", time / 60, time % 60); + lv_obj_align_to(label2, obj_base, LV_ALIGN_BOTTOM_LEFT, 0, 0); + + lv_obj_t* label3 = lv_label_create(obj_base); + lv_obj_set_style_text_font( + label3, + resource_get_font(BANDX_REGULAR_FONT "_20"), + LV_PART_MAIN); + lv_obj_set_style_text_color( + label3, + lv_color_hex(ctx->sleep_type_color[sleep_info[i].type]), + LV_PART_MAIN); + uint32_t time_len = ctx->sleep_time_grp[0].end_min - ctx->sleep_time_grp[0].start_min; + lv_label_set_text_fmt(label3, "%" LV_PRIu32 "%%", time * 100 / time_len); + lv_obj_align_to(label3, obj_base, LV_ALIGN_BOTTOM_RIGHT, 0, 0); + } +} + +static void auto_event_create(page_ctx_t* ctx) +{ + lv_auto_event_data_t ae_grp[EVENT_CNT] = { + { &ctx->img_down, LV_EVENT_CLICKED, 2000 }, + { &ctx->img_up, LV_EVENT_CLICKED, 2000 }, + { &ctx->base.obj, LV_EVENT_LEAVE, 2000 }, + }; + for (int i = 0; i < EVENT_CNT; i++) { + ctx->event_data[i] = ae_grp[i]; + } + + AUTO_EVENT_CREATE(ctx->auto_event, ctx->event_data); +} + +static void on_root_event(lv_event_t* e) +{ + lv_obj_t* root = lv_event_get_current_target_obj(e); + lv_event_code_t code = lv_event_get_code(e); + page_ctx_t* ctx = lv_obj_get_user_data(root); + + if (code == LV_EVENT_GESTURE) { + lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act()); + if (dir == LV_DIR_RIGHT) { + lv_obj_send_event(root, LV_EVENT_LEAVE, NULL); + } else if (dir == LV_DIR_BOTTOM) { + CONT_SLEEP_MOVE_DOWN(false, ctx->cont_sleep); + } else if (dir == LV_DIR_TOP) { + CONT_SLEEP_MOVE_DOWN(true, ctx->cont_sleep); + } + } else if (code == LV_EVENT_LEAVE) { + page_pop(&ctx->base); + } +} + +static void on_page_construct(lv_fragment_t* self, void* args) +{ + LV_LOG_INFO("self: %p args: %p", self, args); + + page_ctx_t* ctx = (page_ctx_t*)self; + + uint32_t sleep_type_color[SLEEP_TYPE_MAX] = { + 0x3f71f5, + 0x023192, + 0x7ac943, + 0xff931e, + }; + + for (int i = 0; i < SLEEP_TYPE_MAX; i++) { + ctx->sleep_type_color[i] = sleep_type_color[i]; + } + + sleep_time_t sleep_time_grp[SLEEP_TIME_CNT] = { + { SLEEP_TYPE_SHALLOW, 29, 588 }, + { SLEEP_TYPE_DEEP, 61, 80 }, + { SLEEP_TYPE_DEEP, 120, 200 }, + { SLEEP_TYPE_REM, 80, 95 }, + { SLEEP_TYPE_REM, 250, 310 }, + { SLEEP_TYPE_AWAKE, 400, 410 }, + }; + + for (int i = 0; i < SLEEP_TIME_CNT; i++) { + ctx->sleep_time_grp[i] = sleep_time_grp[i]; + } + + sleep_info_t sleep_info_grp[SLEEP_INFO_CNT] = { + { "Shallow sleep", SLEEP_TYPE_SHALLOW }, + { "Deep sleep", SLEEP_TYPE_DEEP }, + { "REM", SLEEP_TYPE_REM }, + { "Awake", SLEEP_TYPE_AWAKE }, + }; + + for (int i = 0; i < SLEEP_INFO_CNT; i++) { + ctx->sleep_info_grp[i] = sleep_info_grp[i]; + } +} + +static void on_page_destruct(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static void on_page_attached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static void on_page_detached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container) +{ + LV_LOG_INFO("self: %p container: %p", self, container); + + lv_obj_t* root = lv_obj_create(container); + lv_obj_remove_style_all(root); + lv_obj_add_style(root, resource_get_style("root_def"), 0); + lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL); + lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE); + lv_obj_clear_flag(root, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_user_data(root, self); + return root; +} + +static void on_page_created(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + + page_ctx_t* ctx = (page_ctx_t*)self; + + cont_sleep_create(obj); + + img_arrow_create(ctx->cont_sleep); + img_sleep_create(ctx->cont_sleep); + label_score_create(ctx->cont_sleep); + label_total_time_create(ctx->cont_sleep); + + obj_sleep_time_create(ctx->cont_sleep, ctx->sleep_time_grp, SLEEP_TIME_CNT); + obj_sleep_info_create(ctx->cont_sleep, ctx->sleep_info_grp, SLEEP_INFO_CNT); + auto_event_create(ctx); +} + +static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + + page_ctx_t* ctx = (page_ctx_t*)self; + AUTO_EVENT_DELETE(ctx->auto_event); +} + +static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); +} + +static bool on_page_event(lv_fragment_t* self, int code, void* user_data) +{ + LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data); + return false; +} + +PAGE_CLASS_DEF(sleep); diff --git a/bandx/page/sport.c b/bandx/page/sport.c new file mode 100644 index 0000000000000000000000000000000000000000..3cfd43ccd9e81e8fcfa150f8788b02274bf64f81 --- /dev/null +++ b/bandx/page/sport.c @@ -0,0 +1,233 @@ +/** + * @file sport.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "page.h" + +/********************* + * DEFINES + *********************/ + +#define BTN_WIDTH 170 +#define BTN_HEIGHT 50 +#define SPORT_INFO_CNT 9 +#define EVENT_CNT 2 + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + const char* img_src_name; + const char* sport_name; + uint32_t color; +} sport_info_t; + +typedef struct { + lv_fragment_t base; + lv_obj_t* list; + sport_info_t sport_info_grp[SPORT_INFO_CNT]; + lv_auto_event_data_t event_data[EVENT_CNT]; + lv_auto_event_t* auto_event; +} page_ctx_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void on_sport_icon_event(lv_event_t* event) +{ + lv_event_code_t code = lv_event_get_code(event); + if (code == LV_EVENT_CLICKED) { + LV_LOG_WARN("NO match for this sport."); + } +} +static void on_sport_list_event(lv_event_t* event) +{ + lv_event_code_t code = lv_event_get_code(event); + lv_obj_t* last_obj = lv_event_get_user_data(event); + if (code == LV_EVENT_VALUE_CHANGED) { + lv_obj_scroll_to_view(last_obj, LV_ANIM_ON); + } +} + +static void sport_title_create(lv_obj_t* par) +{ + lv_obj_t* obj = lv_obj_create(par); + lv_obj_remove_style_all(obj); + lv_obj_set_size(obj, PAGE_HOR_RES, 35); + lv_obj_align(obj, LV_ALIGN_TOP_MID, 0, 0); + + lv_obj_t* label = lv_label_create(obj); + lv_label_set_text(label, "Sports"); + lv_obj_set_style_text_color(label, lv_color_white(), LV_PART_MAIN); + lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_15"), LV_PART_MAIN); + lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); +} + +static void sport_list_create(page_ctx_t* ctx) +{ + ctx->list = lv_obj_create(ctx->base.obj); + lv_obj_remove_style_all(ctx->list); + lv_obj_set_size(ctx->list, PAGE_HOR_RES, PAGE_VER_RES - 40); + lv_obj_set_flex_flow(ctx->list, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(ctx->list, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_row(ctx->list, 10, 0); + lv_obj_align(ctx->list, LV_ALIGN_TOP_MID, 0, 40); + + lv_obj_t* obj_base = NULL; + + for (int i = 0; i < SPORT_INFO_CNT; i++) { + + obj_base = lv_btn_create(ctx->list); + lv_obj_set_style_bg_color(obj_base, lv_color_hex(0x666666), LV_PART_MAIN); + lv_obj_set_style_border_width(obj_base, 0, LV_PART_MAIN); + lv_obj_set_size(obj_base, BTN_WIDTH, BTN_HEIGHT); + lv_obj_set_style_pad_left(obj_base, 10, LV_PART_MAIN); + lv_obj_add_event(obj_base, on_sport_icon_event, LV_EVENT_ALL, NULL); + + lv_obj_t* img = lv_img_create(obj_base); + lv_img_set_src(img, resource_get_img(ctx->sport_info_grp[i].img_src_name)); + lv_obj_set_style_image_recolor_opa(img, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_image_recolor(img, lv_color_hex(ctx->sport_info_grp[i].color), LV_PART_MAIN); + lv_obj_align(img, LV_ALIGN_LEFT_MID, 0, 0); + + lv_obj_t* label = lv_label_create(obj_base); + lv_label_set_text(label, ctx->sport_info_grp[i].sport_name); + lv_obj_set_style_text_color(label, lv_color_white(), LV_PART_MAIN); + lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_15"), LV_PART_MAIN); + lv_obj_align_to(label, img, LV_ALIGN_OUT_RIGHT_MID, 4, 0); + } + lv_obj_add_event(ctx->list, on_sport_list_event, LV_EVENT_ALL, obj_base); +} + +static void auto_event_create(page_ctx_t* ctx) +{ + lv_auto_event_data_t ae_grp[EVENT_CNT] = { + { &ctx->list, LV_EVENT_VALUE_CHANGED, 1000 }, + { &ctx->base.obj, LV_EVENT_LEAVE, 2000 }, + }; + for (int i = 0; i < EVENT_CNT; i++) { + ctx->event_data[i] = ae_grp[i]; + } + AUTO_EVENT_CREATE(ctx->auto_event, ctx->event_data); +} + +static void on_root_event(lv_event_t* e) +{ + lv_obj_t* root = lv_event_get_current_target_obj(e); + lv_event_code_t code = lv_event_get_code(e); + page_ctx_t* ctx = lv_obj_get_user_data(root); + + if (code == LV_EVENT_GESTURE) { + lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act()); + if (dir == LV_DIR_RIGHT) { + lv_obj_send_event(root, LV_EVENT_LEAVE, NULL); + } + } else if (code == LV_EVENT_LEAVE) { + page_pop(&ctx->base); + } +} + +static void on_page_construct(lv_fragment_t* self, void* args) +{ + LV_LOG_INFO("self: %p args: %p", self, args); + + page_ctx_t* ctx = (page_ctx_t*)self; + + sport_info_t sport_info_grp[SPORT_INFO_CNT] = { + { "icon_running", "Running", 0x9AFF9A }, + { "icon_stretching", "Stretching", 0xFFF68F }, + { "icon_riding", "Riding", 0xFFC1C1 }, + { "icon_skiing", "Skiing", 0xAB82FF }, + { "icon_yoga", "Yoga", 0x97FFFF }, + { "icon_tabletennis", "Table tennis", 0xFF83FA }, + { "icon_basketball", "Basketball", 0xFFA54F }, + { "icon_volleyball", "Volleyball", 0x87CEFA }, + { "icon_more_sports", "More sports", 0x4876FF }, + }; + + for (int i = 0; i < SPORT_INFO_CNT; i++) { + ctx->sport_info_grp[i] = sport_info_grp[i]; + } +} + +static void on_page_destruct(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static void on_page_attached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static void on_page_detached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container) +{ + LV_LOG_INFO("self: %p container: %p", self, container); + + lv_obj_t* root = lv_obj_create(container); + lv_obj_remove_style_all(root); + lv_obj_add_style(root, resource_get_style("root_def"), 0); + lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL); + lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE); + lv_obj_clear_flag(root, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_user_data(root, self); + return root; +} + +static void on_page_created(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + + page_ctx_t* ctx = (page_ctx_t*)self; + + sport_title_create(obj); + sport_list_create(ctx); + auto_event_create(ctx); +} + +static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + page_ctx_t* ctx = (page_ctx_t*)self; + AUTO_EVENT_DELETE(ctx->auto_event); +} + +static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); +} + +static bool on_page_event(lv_fragment_t* self, int code, void* user_data) +{ + LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data); + return false; +} + +PAGE_CLASS_DEF(sport); diff --git a/bandx/page/stop_watch.c b/bandx/page/stop_watch.c new file mode 100644 index 0000000000000000000000000000000000000000..5ba72497873ef56025ebbd6a20883a11f96a7d0c --- /dev/null +++ b/bandx/page/stop_watch.c @@ -0,0 +1,417 @@ +/** + * @file stop_watch.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "page.h" + +#include "../utils/lv_obj_ext_func.h" + +/********************* + * DEFINES + *********************/ + +#define BTN_WIDTH 83 +#define BTN_HEIGHT 60 +#define BTN_CNT 2 +#define EVENT_CNT 10 + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + const char* img_src; + lv_obj_t* btn; + lv_obj_t* img; +} btn_sw_t; + +typedef struct { + lv_obj_t* list_btn; + const char* text; + const uint16_t btn_val; +} list_btn_t; + +enum BtnGrpEnum { + BTN_START_PAUSE, + BTN_FLAG_RESET, +}; + +typedef struct { + lv_fragment_t base; + lv_obj_t* label_time; + lv_obj_t* list_history; + lv_timer_t* timer_sw_update; + btn_sw_t btn_grp[BTN_CNT]; + + lv_coord_t label_time_record_y; + lv_coord_t label_time_ready_y; + + bool sw_is_pause; + uint32_t sw_current_time; + uint32_t sw_interval_time; + uint32_t sw_last_time; + uint8_t sw_history_record_cnt; + lv_auto_event_data_t event_data[EVENT_CNT]; + lv_auto_event_t* auto_event; +} page_ctx_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void line_sw_create(lv_obj_t* par) +{ + static const lv_point_precise_t line_points[] = { { 0, 0 }, { 170, 0 } }; + + lv_obj_t* line = lv_line_create(par); + lv_line_set_points(line, line_points, ARRAY_SIZE(line_points)); + + lv_obj_set_style_line_color( + line, + lv_color_make(0x80, 0x80, 0x80), + LV_STATE_DEFAULT); + lv_obj_set_style_line_width(line, 2, LV_STATE_DEFAULT); + lv_obj_align_to(line, par, LV_ALIGN_TOP_MID, 0, 286); +} + +static void list_history_create(lv_obj_t* par) +{ + page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par); + + lv_obj_t* list = lv_list_create(par); + lv_obj_set_style_text_font(list, resource_get_font(BANDX_REGULAR_FONT "_20"), LV_PART_MAIN); + lv_obj_set_style_bg_color(list, lv_color_black(), LV_PART_MAIN); + lv_obj_set_style_border_width(list, 0, LV_PART_MAIN); + + lv_obj_set_size(list, 170, 170); + lv_obj_align(list, LV_ALIGN_CENTER, 0, 0); + ctx->list_history = list; +} + +static void label_time_create(lv_obj_t* par) +{ + page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par); + + lv_obj_t* label = lv_label_create(par); + lv_obj_set_style_text_font(label, resource_get_font(BANDX_REGULAR_FONT "_38"), LV_PART_MAIN); + lv_obj_set_style_text_color(label, lv_color_hex(0x87FFCE), LV_PART_MAIN); + + lv_label_set_text(label, "00:00.000"); + lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_align(label, LV_ALIGN_TOP_MID, 0, ctx->label_time_ready_y); + lv_obj_set_width(label, 180); + lv_obj_set_style_border_color(label, lv_color_make(0x80, 0x80, 0x80), LV_PART_MAIN); + lv_obj_set_style_border_width(label, 2, LV_PART_MAIN); + lv_obj_set_style_border_side(label, LV_BORDER_SIDE_BOTTOM, LV_PART_MAIN); + ctx->label_time = label; +} + +static void sw_show_time(uint32_t ms, lv_obj_t* label_time) +{ + uint16_t min = 0, sec = 0, msec = 0; + min = ms / 60000; + sec = (ms - (min * 60000)) / 1000; + msec = ms - (sec * 1000) - (min * 60000); + lv_label_set_text_fmt(label_time, "%02d:%02d.%03d", min, sec, msec); +} + +static void sw_pause(page_ctx_t* ctx) +{ + if (ctx->sw_is_pause) { + ctx->sw_interval_time = lv_tick_get() - ctx->sw_last_time; + lv_timer_resume(ctx->timer_sw_update); + ctx->sw_is_pause = false; + } else { + ctx->sw_last_time = ctx->sw_current_time; + lv_timer_pause(ctx->timer_sw_update); + ctx->sw_is_pause = true; + } +} + +static void sw_reset(page_ctx_t* ctx) +{ + ctx->sw_last_time = 0; + ctx->sw_current_time = 0; + ctx->sw_interval_time = 0; + ctx->sw_is_pause = true; + ctx->sw_history_record_cnt = 0; + lv_timer_pause(ctx->timer_sw_update); + sw_show_time(ctx->sw_current_time, ctx->label_time); + lv_obj_clean(ctx->list_history); + + if (lv_obj_get_y(ctx->label_time) == ctx->label_time_record_y) { + LV_OBJ_ADD_ANIM(ctx->label_time, y, ctx->label_time_ready_y, 500); + } +} + +static void sw_update(lv_timer_t* timer) +{ + page_ctx_t* ctx = lv_timer_get_user_data(timer); + + ctx->sw_current_time = lv_tick_get() - ctx->sw_interval_time; + sw_show_time(ctx->sw_current_time, ctx->label_time); +} + +static void sw_record(page_ctx_t* ctx) +{ + lv_obj_t* list_btn; + lv_obj_t* spangroup; + uint16_t min = 0, sec = 0, msec = 0; + + if (ctx->sw_history_record_cnt < 10 && !ctx->sw_is_pause) { + min = ctx->sw_current_time / 60000; + sec = (ctx->sw_current_time - (min * 60000)) / 1000; + msec = ctx->sw_current_time - (sec * 1000) - (min * 60000); + + char cnt_buf[8]; + char time_buf[64]; + + lv_snprintf(cnt_buf, sizeof(cnt_buf), "%d. ", ctx->sw_history_record_cnt + 1); + lv_snprintf(time_buf, sizeof(time_buf), "%02d:%02d.%03d", min, sec, msec); + + list_btn = lv_list_add_btn(ctx->list_history, NULL, NULL); + lv_obj_clear_flag(list_btn, LV_OBJ_FLAG_CLICKABLE); + lv_obj_set_style_border_width(list_btn, 2, LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(list_btn, 0, LV_PART_MAIN); + + spangroup = lv_spangroup_create(list_btn); + + lv_span_t* cnt_span = lv_spangroup_new_span(spangroup); + lv_span_set_text(cnt_span, cnt_buf); + lv_style_set_text_color(&cnt_span->style, lv_color_hex(0x87FFCE)); + + lv_span_t* time_span = lv_spangroup_new_span(spangroup); + lv_span_set_text(time_span, time_buf); + lv_style_set_text_color(&time_span->style, lv_color_hex(0xffffff)); + + lv_obj_add_state(list_btn, LV_STATE_FOCUSED); + lv_obj_set_style_bg_color(list_btn, lv_color_black(), 0); + lv_obj_set_style_text_decor( + list_btn, + LV_TEXT_DECOR_NONE, + LV_STATE_FOCUSED); + ctx->sw_history_record_cnt++; + } + + if (lv_obj_get_y(ctx->label_time) == ctx->label_time_ready_y) { + LV_OBJ_ADD_ANIM(ctx->label_time, y, ctx->label_time_record_y, 500); + } +} + +static void btn_grp_event_handler(lv_event_t* event) +{ + lv_event_code_t code = lv_event_get_code(event); + lv_obj_t* obj = lv_event_get_current_target_obj(event); + page_ctx_t* ctx = (page_ctx_t*)lv_event_get_user_data(event); + + if (code == LV_EVENT_CLICKED) { + lv_obj_t* img = lv_obj_get_child(obj, 0); + const char* img_src = lv_img_get_src(img); + + if (obj == ctx->btn_grp[BTN_START_PAUSE].btn) { + sw_pause(ctx); + if (ctx->sw_is_pause) { + lv_img_set_src(ctx->btn_grp[BTN_START_PAUSE].img, resource_get_img("icon_start")); + lv_img_set_src(ctx->btn_grp[BTN_FLAG_RESET].img, resource_get_img("icon_reset")); + } else { + lv_img_set_src(ctx->btn_grp[BTN_START_PAUSE].img, resource_get_img("icon_pause")); + lv_img_set_src(ctx->btn_grp[BTN_FLAG_RESET].img, resource_get_img("icon_flag")); + lv_obj_clear_state(ctx->btn_grp[BTN_FLAG_RESET].btn, LV_STATE_DISABLED); + } + } else if (obj == ctx->btn_grp[BTN_FLAG_RESET].btn) { + if (lv_strcmp(img_src, resource_get_img("icon_flag")) == 0) { + sw_record(ctx); + } else { + if (ctx->sw_is_pause) { + sw_reset(ctx); + lv_obj_add_state(ctx->btn_grp[BTN_FLAG_RESET].btn, LV_STATE_DISABLED); + ctx->sw_history_record_cnt = 0; + } + } + } + } +} + +static void btn_grp_create(lv_obj_t* par, btn_sw_t* btn_sw, int len) +{ + page_ctx_t* ctx = (page_ctx_t*)lv_obj_get_user_data(par); + + for (int i = 0; i < len; i++) { + lv_obj_t* btn = lv_btn_create(par); + lv_obj_set_size(btn, BTN_WIDTH, BTN_HEIGHT); + + lv_coord_t x_ofs = BTN_WIDTH / 2 + 3; + lv_obj_align(btn, LV_ALIGN_TOP_MID, i == 0 ? -x_ofs : x_ofs, 300); + lv_obj_set_style_border_color(btn, lv_color_black(), LV_STATE_DEFAULT); + lv_obj_set_style_border_width(btn, 2, LV_STATE_DEFAULT); + lv_obj_set_style_bg_color( + btn, + lv_palette_main(LV_PALETTE_GREY), + LV_STATE_PRESSED || LV_STATE_DISABLED); + lv_obj_set_style_bg_color( + btn, + lv_color_make(0x87, 0xFF, 0xCE), + LV_STATE_DEFAULT); + lv_obj_set_style_radius(btn, 10, LV_PART_MAIN); + lv_obj_add_event(btn, btn_grp_event_handler, LV_EVENT_ALL, ctx); + + lv_obj_t* img = lv_img_create(btn); + lv_img_set_src(img, resource_get_img(btn_sw[i].img_src)); + lv_obj_align_to(img, btn, LV_ALIGN_CENTER, 0, 0); + + btn_sw[i].btn = btn; + btn_sw[i].img = img; + } +} + +static void auto_event_create(page_ctx_t* ctx) +{ + btn_sw_t* btn_sw = ctx->btn_grp; + lv_auto_event_data_t ae_grp[EVENT_CNT] = { + { &(btn_sw[BTN_START_PAUSE].btn), LV_EVENT_CLICKED, 2000 }, + { &(btn_sw[BTN_FLAG_RESET].btn), LV_EVENT_CLICKED, 453 }, + { &(btn_sw[BTN_FLAG_RESET].btn), LV_EVENT_CLICKED, 888 }, + { &(btn_sw[BTN_FLAG_RESET].btn), LV_EVENT_CLICKED, 1605 }, + { &(btn_sw[BTN_FLAG_RESET].btn), LV_EVENT_CLICKED, 785 }, + { &(btn_sw[BTN_FLAG_RESET].btn), LV_EVENT_CLICKED, 1200 }, + { &(btn_sw[BTN_FLAG_RESET].btn), LV_EVENT_CLICKED, 1456 }, + { &(btn_sw[BTN_START_PAUSE].btn), LV_EVENT_CLICKED, 2000 }, + { &(btn_sw[BTN_FLAG_RESET].btn), LV_EVENT_CLICKED, 1000 }, + { &ctx->base.obj, LV_EVENT_LEAVE, 2000 }, + }; + for (int i = 0; i < EVENT_CNT; i++) { + ctx->event_data[i] = ae_grp[i]; + } + + AUTO_EVENT_CREATE(ctx->auto_event, ctx->event_data); +} + +static void on_root_event(lv_event_t* e) +{ + lv_obj_t* root = lv_event_get_current_target_obj(e); + lv_event_code_t code = lv_event_get_code(e); + page_ctx_t* ctx = lv_obj_get_user_data(root); + + if (code == LV_EVENT_GESTURE) { + lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act()); + if (dir == LV_DIR_RIGHT) { + lv_obj_send_event(root, LV_EVENT_LEAVE, NULL); + } + } else if (code == LV_EVENT_LEAVE) { + page_pop(&ctx->base); + } +} + +static void on_page_construct(lv_fragment_t* self, void* args) +{ + LV_LOG_INFO("self: %p args: %p", self, args); + + page_ctx_t* ctx = (page_ctx_t*)self; + + btn_sw_t btn_grp[BTN_CNT] = { + { "icon_start" }, + { "icon_reset" }, + }; + + for (int i = 0; i < BTN_CNT; i++) { + ctx->btn_grp[i] = btn_grp[i]; + } + + ctx->label_time_record_y = 18; + ctx->label_time_ready_y = 64; + + ctx->sw_is_pause = true; + ctx->sw_current_time = 0; + ctx->sw_interval_time = 0; + ctx->sw_last_time = 0; + ctx->sw_history_record_cnt = 0; +} + +static void on_page_destruct(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static void on_page_attached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static void on_page_detached(lv_fragment_t* self) +{ + LV_LOG_INFO("self: %p", self); +} + +static lv_obj_t* on_page_create(lv_fragment_t* self, lv_obj_t* container) +{ + LV_LOG_INFO("self: %p container: %p", self, container); + + lv_obj_t* root = lv_obj_create(container); + lv_obj_remove_style_all(root); + lv_obj_add_style(root, resource_get_style("root_def"), 0); + lv_obj_add_event(root, on_root_event, LV_EVENT_ALL, NULL); + lv_obj_clear_flag(root, LV_OBJ_FLAG_GESTURE_BUBBLE); + lv_obj_clear_flag(root, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_user_data(root, self); + return root; +} + +static void on_page_created(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + + page_ctx_t* ctx = (page_ctx_t*)self; + + ctx->timer_sw_update = lv_timer_create(sw_update, 51, ctx); + + list_history_create(obj); + label_time_create(obj); + btn_grp_create(obj, ctx->btn_grp, BTN_CNT); + line_sw_create(obj); + auto_event_create(ctx); + + ctx->sw_is_pause ? lv_timer_pause(ctx->timer_sw_update) : lv_timer_resume(ctx->timer_sw_update); +} + +static void on_page_will_delete(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); + + page_ctx_t* ctx = (page_ctx_t*)self; + + lv_timer_del(ctx->timer_sw_update); + AUTO_EVENT_DELETE(ctx->auto_event); +} + +static void on_page_deleted(lv_fragment_t* self, lv_obj_t* obj) +{ + LV_LOG_INFO("self: %p obj: %p", self, obj); +} + +static bool on_page_event(lv_fragment_t* self, int code, void* user_data) +{ + LV_LOG_INFO("self: %p code: %d user_data: %p", self, code, user_data); + return false; +} + +PAGE_CLASS_DEF(stop_watch); diff --git a/bandx/resource/font/assets/AlibabaPuHuiTi-3-55-Regular.ttf b/bandx/resource/font/assets/AlibabaPuHuiTi-3-55-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a6eaf3613ee1160caf604ab3f11b303fdb5f8668 Binary files /dev/null and b/bandx/resource/font/assets/AlibabaPuHuiTi-3-55-Regular.ttf differ diff --git a/bandx/resource/font/assets/AlibabaPuHuiTi-3-95-ExtraBold.ttf b/bandx/resource/font/assets/AlibabaPuHuiTi-3-95-ExtraBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..79f26640f72fa5b81f9ebf915cd29319eba487d7 Binary files /dev/null and b/bandx/resource/font/assets/AlibabaPuHuiTi-3-95-ExtraBold.ttf differ diff --git a/bandx/resource/font/font.inc b/bandx/resource/font/font.inc new file mode 100644 index 0000000000000000000000000000000000000000..da9bb9defa752d7fe61ce969a75a17ec62ada08c --- /dev/null +++ b/bandx/resource/font/font.inc @@ -0,0 +1,8 @@ +/* font list */ +FONT_DEF(AlibabaPuHuiTi-3-55-Regular, 15) +FONT_DEF(AlibabaPuHuiTi-3-55-Regular, 20) +FONT_DEF(AlibabaPuHuiTi-3-55-Regular, 38) +FONT_DEF(AlibabaPuHuiTi-3-55-Regular, 72) +FONT_DEF(AlibabaPuHuiTi-3-95-ExtraBold, 23) +FONT_DEF(AlibabaPuHuiTi-3-95-ExtraBold, 28) +FONT_DEF(AlibabaPuHuiTi-3-95-ExtraBold, 110) \ No newline at end of file diff --git a/bandx/resource/image/assets/arrow_down.png b/bandx/resource/image/assets/arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..90e532f8b777e4bf45bfdb8cb2f6fdfaa11146de Binary files /dev/null and b/bandx/resource/image/assets/arrow_down.png differ diff --git a/bandx/resource/image/assets/arrow_up.png b/bandx/resource/image/assets/arrow_up.png new file mode 100644 index 0000000000000000000000000000000000000000..b93a69d3f0235b06ffa069c7d00aff4c080f6e10 Binary files /dev/null and b/bandx/resource/image/assets/arrow_up.png differ diff --git a/bandx/resource/image/assets/centigrade.png b/bandx/resource/image/assets/centigrade.png new file mode 100644 index 0000000000000000000000000000000000000000..0eb787adf935d104370ce00d2b9ce282747b21fd Binary files /dev/null and b/bandx/resource/image/assets/centigrade.png differ diff --git a/bandx/resource/image/assets/flashlight.png b/bandx/resource/image/assets/flashlight.png new file mode 100644 index 0000000000000000000000000000000000000000..45a6a78609a2e8504f46e7008f48ad2d416ef0ff Binary files /dev/null and b/bandx/resource/image/assets/flashlight.png differ diff --git a/bandx/resource/image/assets/heart.png b/bandx/resource/image/assets/heart.png new file mode 100644 index 0000000000000000000000000000000000000000..59f72d3a58fd758e2929f7ff1885bc641604bd36 Binary files /dev/null and b/bandx/resource/image/assets/heart.png differ diff --git a/bandx/resource/image/assets/heart_color.png b/bandx/resource/image/assets/heart_color.png new file mode 100644 index 0000000000000000000000000000000000000000..c0117de706f65b467f72eeaa5617a5335875212a Binary files /dev/null and b/bandx/resource/image/assets/heart_color.png differ diff --git a/bandx/resource/image/assets/heart_rate.png b/bandx/resource/image/assets/heart_rate.png new file mode 100644 index 0000000000000000000000000000000000000000..393544fa58b4ef0fce0ee83d6291a6e1f976a1a9 Binary files /dev/null and b/bandx/resource/image/assets/heart_rate.png differ diff --git a/bandx/resource/image/assets/icon_basketball.png b/bandx/resource/image/assets/icon_basketball.png new file mode 100644 index 0000000000000000000000000000000000000000..68973a0eb939e25a20e801cb6614bfccc8b5f562 Binary files /dev/null and b/bandx/resource/image/assets/icon_basketball.png differ diff --git a/bandx/resource/image/assets/icon_battery.png b/bandx/resource/image/assets/icon_battery.png new file mode 100644 index 0000000000000000000000000000000000000000..546096f08e82f61ff34d089db66044965f7fc3cb Binary files /dev/null and b/bandx/resource/image/assets/icon_battery.png differ diff --git a/bandx/resource/image/assets/icon_bluetooth.png b/bandx/resource/image/assets/icon_bluetooth.png new file mode 100644 index 0000000000000000000000000000000000000000..9b5fe0c50fc334d484833df8b5a6bae23c4ed666 Binary files /dev/null and b/bandx/resource/image/assets/icon_bluetooth.png differ diff --git a/bandx/resource/image/assets/icon_flag.png b/bandx/resource/image/assets/icon_flag.png new file mode 100644 index 0000000000000000000000000000000000000000..3b4c8e94187417e93223ce0639ebfc86806ff503 Binary files /dev/null and b/bandx/resource/image/assets/icon_flag.png differ diff --git a/bandx/resource/image/assets/icon_minus.png b/bandx/resource/image/assets/icon_minus.png new file mode 100644 index 0000000000000000000000000000000000000000..db7467c5c7534e0ae8b1ff328a0203b86ee3bdcc Binary files /dev/null and b/bandx/resource/image/assets/icon_minus.png differ diff --git a/bandx/resource/image/assets/icon_more_sports.png b/bandx/resource/image/assets/icon_more_sports.png new file mode 100644 index 0000000000000000000000000000000000000000..f98e4234800defb0e1362ad94e3a531814b59c09 Binary files /dev/null and b/bandx/resource/image/assets/icon_more_sports.png differ diff --git a/bandx/resource/image/assets/icon_next.png b/bandx/resource/image/assets/icon_next.png new file mode 100644 index 0000000000000000000000000000000000000000..a3545ce2489c2a387775c1f0da6ebe51f23530f5 Binary files /dev/null and b/bandx/resource/image/assets/icon_next.png differ diff --git a/bandx/resource/image/assets/icon_pause.png b/bandx/resource/image/assets/icon_pause.png new file mode 100644 index 0000000000000000000000000000000000000000..e7ac579c25e5e1feb8b2884091843efc96c832f5 Binary files /dev/null and b/bandx/resource/image/assets/icon_pause.png differ diff --git a/bandx/resource/image/assets/icon_plus.png b/bandx/resource/image/assets/icon_plus.png new file mode 100644 index 0000000000000000000000000000000000000000..27f41d65a1f7e44d4719809169380721a8890c7c Binary files /dev/null and b/bandx/resource/image/assets/icon_plus.png differ diff --git a/bandx/resource/image/assets/icon_prev.png b/bandx/resource/image/assets/icon_prev.png new file mode 100644 index 0000000000000000000000000000000000000000..2f3d62a057aa7d82b604a06bd2a9f148ca89fd67 Binary files /dev/null and b/bandx/resource/image/assets/icon_prev.png differ diff --git a/bandx/resource/image/assets/icon_reset.png b/bandx/resource/image/assets/icon_reset.png new file mode 100644 index 0000000000000000000000000000000000000000..c61ea2ef94b5bc384b0e9ad8278e15a17ca109cc Binary files /dev/null and b/bandx/resource/image/assets/icon_reset.png differ diff --git a/bandx/resource/image/assets/icon_riding.png b/bandx/resource/image/assets/icon_riding.png new file mode 100644 index 0000000000000000000000000000000000000000..70d607bbd459386ac9ad407a35f9c5ea9d10fb11 Binary files /dev/null and b/bandx/resource/image/assets/icon_riding.png differ diff --git a/bandx/resource/image/assets/icon_running.png b/bandx/resource/image/assets/icon_running.png new file mode 100644 index 0000000000000000000000000000000000000000..0623c81cbdf433eed2131920402f0eadc146b43e Binary files /dev/null and b/bandx/resource/image/assets/icon_running.png differ diff --git a/bandx/resource/image/assets/icon_shadow_down.png b/bandx/resource/image/assets/icon_shadow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..47c48985ec81752c293018bd3f85cfbb3c08d6a5 Binary files /dev/null and b/bandx/resource/image/assets/icon_shadow_down.png differ diff --git a/bandx/resource/image/assets/icon_shadow_up.png b/bandx/resource/image/assets/icon_shadow_up.png new file mode 100644 index 0000000000000000000000000000000000000000..82af8cf81e5a2643d875d68f3d64aa1fef49ebfe Binary files /dev/null and b/bandx/resource/image/assets/icon_shadow_up.png differ diff --git a/bandx/resource/image/assets/icon_skiing.png b/bandx/resource/image/assets/icon_skiing.png new file mode 100644 index 0000000000000000000000000000000000000000..ef2b04ce2af243e70f1be5d51b9c212dbcaca515 Binary files /dev/null and b/bandx/resource/image/assets/icon_skiing.png differ diff --git a/bandx/resource/image/assets/icon_start.png b/bandx/resource/image/assets/icon_start.png new file mode 100644 index 0000000000000000000000000000000000000000..5006892b7dedefcfa2cc52560f322f3770cfa594 Binary files /dev/null and b/bandx/resource/image/assets/icon_start.png differ diff --git a/bandx/resource/image/assets/icon_stretching.png b/bandx/resource/image/assets/icon_stretching.png new file mode 100644 index 0000000000000000000000000000000000000000..ce8a6740c7826fb97d0f4ceb9e67f72694b7d3e4 Binary files /dev/null and b/bandx/resource/image/assets/icon_stretching.png differ diff --git a/bandx/resource/image/assets/icon_tabletennis.png b/bandx/resource/image/assets/icon_tabletennis.png new file mode 100644 index 0000000000000000000000000000000000000000..97de39a2b067a250e059b675ba738efde9a7a0dd Binary files /dev/null and b/bandx/resource/image/assets/icon_tabletennis.png differ diff --git a/bandx/resource/image/assets/icon_volleyball.png b/bandx/resource/image/assets/icon_volleyball.png new file mode 100644 index 0000000000000000000000000000000000000000..fb1891fbdab1680a4ba8c8824d4bea6da27e7f4a Binary files /dev/null and b/bandx/resource/image/assets/icon_volleyball.png differ diff --git a/bandx/resource/image/assets/icon_volume_add.png b/bandx/resource/image/assets/icon_volume_add.png new file mode 100644 index 0000000000000000000000000000000000000000..84c483a31d7234fff4769c859177ebd05a87c8f6 Binary files /dev/null and b/bandx/resource/image/assets/icon_volume_add.png differ diff --git a/bandx/resource/image/assets/icon_volume_reduce.png b/bandx/resource/image/assets/icon_volume_reduce.png new file mode 100644 index 0000000000000000000000000000000000000000..42633fc4606387fbb5842dd188f9a63344c5412e Binary files /dev/null and b/bandx/resource/image/assets/icon_volume_reduce.png differ diff --git a/bandx/resource/image/assets/icon_yoga.png b/bandx/resource/image/assets/icon_yoga.png new file mode 100644 index 0000000000000000000000000000000000000000..376448d02fe15f0b9f23b1251aeb0915c19bf969 Binary files /dev/null and b/bandx/resource/image/assets/icon_yoga.png differ diff --git a/bandx/resource/image/assets/music.png b/bandx/resource/image/assets/music.png new file mode 100644 index 0000000000000000000000000000000000000000..641e09c1df4a6a034308b4f3fa564a4c41ab78d2 Binary files /dev/null and b/bandx/resource/image/assets/music.png differ diff --git a/bandx/resource/image/assets/num_shadow.png b/bandx/resource/image/assets/num_shadow.png new file mode 100644 index 0000000000000000000000000000000000000000..30174950769ed56a9e32c096f540061bfb045661 Binary files /dev/null and b/bandx/resource/image/assets/num_shadow.png differ diff --git a/bandx/resource/image/assets/settings.png b/bandx/resource/image/assets/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..96b0721bb74a8f7f13bbe38f70784aa68b03db8a Binary files /dev/null and b/bandx/resource/image/assets/settings.png differ diff --git a/bandx/resource/image/assets/sleep.png b/bandx/resource/image/assets/sleep.png new file mode 100644 index 0000000000000000000000000000000000000000..c34f3fe816a36207365275901ed99a79f9e1a4a6 Binary files /dev/null and b/bandx/resource/image/assets/sleep.png differ diff --git a/bandx/resource/image/assets/sport.png b/bandx/resource/image/assets/sport.png new file mode 100644 index 0000000000000000000000000000000000000000..8bf1f8ac29a8af7881b3b3cba3657072069929d0 Binary files /dev/null and b/bandx/resource/image/assets/sport.png differ diff --git a/bandx/resource/image/assets/step.png b/bandx/resource/image/assets/step.png new file mode 100644 index 0000000000000000000000000000000000000000..708614c1450adddcebefb68111c75812531d4563 Binary files /dev/null and b/bandx/resource/image/assets/step.png differ diff --git a/bandx/resource/image/assets/stop_watch.png b/bandx/resource/image/assets/stop_watch.png new file mode 100644 index 0000000000000000000000000000000000000000..acc2e9521065c3bc27f90393ac8a96c62c98cce6 Binary files /dev/null and b/bandx/resource/image/assets/stop_watch.png differ diff --git a/bandx/resource/image/assets/weather.png b/bandx/resource/image/assets/weather.png new file mode 100644 index 0000000000000000000000000000000000000000..77dc8dbc4443a0d23f3407bc46845531d97d103d Binary files /dev/null and b/bandx/resource/image/assets/weather.png differ diff --git a/bandx/resource/image/img_src.inc b/bandx/resource/image/img_src.inc new file mode 100644 index 0000000000000000000000000000000000000000..c373bb160450b6fd27c5d8e432da74694815dbd8 --- /dev/null +++ b/bandx/resource/image/img_src.inc @@ -0,0 +1,39 @@ +/* img list */ +IMG_DEF(arrow_down) +IMG_DEF(arrow_up) +IMG_DEF(icon_battery) +IMG_DEF(icon_bluetooth) +IMG_DEF(centigrade) +IMG_DEF(flashlight) +IMG_DEF(heart_color) +IMG_DEF(heart_rate) +IMG_DEF(heart) +IMG_DEF(icon_flag) +IMG_DEF(icon_minus) +IMG_DEF(icon_next) +IMG_DEF(icon_pause) +IMG_DEF(icon_plus) +IMG_DEF(icon_prev) +IMG_DEF(icon_reset) +IMG_DEF(icon_shadow_down) +IMG_DEF(icon_shadow_up) +IMG_DEF(icon_start) +IMG_DEF(icon_volume_add) +IMG_DEF(icon_volume_reduce) +IMG_DEF(music) +IMG_DEF(num_shadow) +IMG_DEF(settings) +IMG_DEF(sleep) +IMG_DEF(sport) +IMG_DEF(step) +IMG_DEF(stop_watch) +IMG_DEF(weather) +IMG_DEF(icon_more_sports) +IMG_DEF(icon_running) +IMG_DEF(icon_stretching) +IMG_DEF(icon_riding) +IMG_DEF(icon_skiing) +IMG_DEF(icon_yoga) +IMG_DEF(icon_tabletennis) +IMG_DEF(icon_basketball) +IMG_DEF(icon_volleyball) diff --git a/bandx/resource/resource.c b/bandx/resource/resource.c new file mode 100644 index 0000000000000000000000000000000000000000..4b7088052c24ae799c72d508c0ce8efaec0dac8e --- /dev/null +++ b/bandx/resource/resource.c @@ -0,0 +1,154 @@ +/** + * @file resource.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "resource.h" + +/********************* + * DEFINES + *********************/ + +#define ARRAY_SIZE(ARRAY) (sizeof(ARRAY) / sizeof(ARRAY[0])) + +/* import images*/ +#define IMG_DEF(NAME) LV_IMG_DECLARE(img_src_##NAME); +#include "image/img_src.inc" +#undef IMG_DEF + +#define FONT_BASE_PATH "/font/" +#define IMG_BASE_PATH "/image/" + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + const char* key; + const char* img_src; +} resource_img_t; + +typedef struct { + const char* key; + const lv_font_t* font; +} resource_font_t; + +typedef struct { + const char* key; + lv_style_t style; +} resource_style_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +#define IMG_DEF(NAME) { #NAME, CONFIG_BANDX_BASE_PATH IMG_BASE_PATH #NAME ".png" }, +static const resource_img_t g_img_resource_map[] = { +#include "image/img_src.inc" +}; +#undef IMG_DEF + +#define FONT_DEF(NAME, SIZE) { "default", LV_FONT_DEFAULT }, +static resource_font_t g_font_resource_map[] = { +#include "font/font.inc" +}; +#undef FONT_DEF + +#define STYLE_DEF(NAME) { "empty" }, +static resource_style_t g_style_resource_map[] = { +#include "style/style.inc" +}; +#undef STYLE_DEF + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void resource_init(void) +{ + /* create fonts */ + int font_index = 0; +#define FONT_DEF(NAME, SIZE) \ + do { \ + lv_font_t* font = lv_freetype_font_create(CONFIG_BANDX_BASE_PATH FONT_BASE_PATH #NAME ".ttf", \ + LV_FREETYPE_FONT_RENDER_MODE_BITMAP, \ + SIZE, \ + LV_FREETYPE_FONT_STYLE_NORMAL); \ + if (font) { \ + g_font_resource_map[font_index].key = #NAME "_" #SIZE; \ + g_font_resource_map[font_index].font = font; \ + font_index++; \ + } else { \ + LV_LOG_WARN("font_" #NAME "_" #SIZE "not found"); \ + } \ + } while (0); +#include "font/font.inc" +#undef FONT_DEF + LV_LOG_USER("create %d fonts", font_index); + + /* init styles */ + int style_index = 0; +#define STYLE_DEF(NAME) \ + do { \ + extern void style_##NAME##_init(lv_style_t* style); \ + g_style_resource_map[style_index].key = #NAME; \ + lv_style_init(&g_style_resource_map[style_index].style); \ + style_##NAME##_init(&g_style_resource_map[style_index].style); \ + style_index++; \ + } while (0); +#include "style/style.inc" +#undef STYLE_DEF + LV_LOG_USER("create %d styles", style_index); +} + +const lv_font_t* resource_get_font(const char* key) +{ + for (int i = 0; i < ARRAY_SIZE(g_font_resource_map); i++) { + if (lv_strcmp(key, g_font_resource_map[i].key) == 0) { + return g_font_resource_map[i].font; + } + } + + LV_LOG_WARN("key %s not found", key); + return LV_FONT_DEFAULT; +} + +const void* resource_get_img(const char* key) +{ + for (int i = 0; i < ARRAY_SIZE(g_img_resource_map); i++) { + if (lv_strcmp(key, g_img_resource_map[i].key) == 0) { + return g_img_resource_map[i].img_src; + } + } + + LV_LOG_WARN("key %s not found", key); + return LV_SYMBOL_IMAGE; +} + +lv_style_t* resource_get_style(const char* key) +{ + for (int i = 0; i < ARRAY_SIZE(g_style_resource_map); i++) { + if (lv_strcmp(key, g_style_resource_map[i].key) == 0) { + return &g_style_resource_map[i].style; + } + } + + LV_LOG_WARN("key %s not found", key); + return &g_style_resource_map[0].style; +} + +/********************** + * STATIC FUNCTIONS + **********************/ diff --git a/bandx/resource/resource.h b/bandx/resource/resource.h new file mode 100644 index 0000000000000000000000000000000000000000..0fe0a7b5617d8dfd6696394f7a104def389bb047 --- /dev/null +++ b/bandx/resource/resource.h @@ -0,0 +1,65 @@ +/** + * @file resource.h + * + */ + +#ifndef RESOURCE_H +#define RESOURCE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include <lvgl/lvgl.h> + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Initialize the resource manager. + */ +void resource_init(void); + +/** + * Get a font resource by key. + * @param key The key associated with the desired font. + * @return A pointer to the font resource, or NULL if not found. + */ +const lv_font_t* resource_get_font(const char* key); + +/** + * Get an image resource by key. + * @param key The key associated with the desired image. + * @return A pointer to the image resource, or NULL if not found. + */ +const void* resource_get_img(const char* key); + +/** + * Get a style resource by key. + * @param key The key associated with the desired style. + * @return A pointer to the style resource, or NULL if not found. + */ +lv_style_t* resource_get_style(const char* key); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*RESOURCE_H*/ diff --git a/bandx/resource/style/style.c b/bandx/resource/style/style.c new file mode 100644 index 0000000000000000000000000000000000000000..c3e02f2f3b9a3e3201fc7b2597167495600303fc --- /dev/null +++ b/bandx/resource/style/style.c @@ -0,0 +1,62 @@ +/** + * @file style.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lvgl/lvgl.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void style_empty_init(lv_style_t* style) +{ +} + +void style_root_def_init(lv_style_t* style) +{ + lv_style_set_bg_color(style, lv_color_black()); + lv_style_set_bg_opa(style, LV_OPA_COVER); + lv_style_set_size(style, LV_PCT(100), LV_PCT(100)); +} + +void style_btn_trans_init(lv_style_t* style) +{ + static const lv_style_prop_t props[] = { + LV_STYLE_BG_COLOR, + LV_STYLE_TRANSFORM_WIDTH, + LV_STYLE_TRANSFORM_HEIGHT, + LV_STYLE_PROP_INV + }; + static lv_style_transition_dsc_t dsc; + lv_style_transition_dsc_init(&dsc, props, lv_anim_path_overshoot, 200, 0, NULL); + lv_style_set_transition(style, &dsc); +} + +/********************** + * STATIC FUNCTIONS + **********************/ diff --git a/bandx/resource/style/style.inc b/bandx/resource/style/style.inc new file mode 100644 index 0000000000000000000000000000000000000000..5a7c1b120794fcf2d2d820cac7006fa4a0b904c7 --- /dev/null +++ b/bandx/resource/style/style.inc @@ -0,0 +1,4 @@ +/* style list */ +STYLE_DEF(empty) +STYLE_DEF(root_def) +STYLE_DEF(btn_trans) diff --git a/bandx/utils/lv_anim_label.c b/bandx/utils/lv_anim_label.c new file mode 100755 index 0000000000000000000000000000000000000000..61f42b8bb8adec174825341fcb3d7fafddfe0e11 --- /dev/null +++ b/bandx/utils/lv_anim_label.c @@ -0,0 +1,349 @@ +/* + * MIT License + * Copyright (c) 2021 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_anim_label.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_anim_label_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void lv_anim_label_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj); +static void lv_anim_label_set_x(void* obj, int32_t x); +static void lv_anim_label_set_y(void* obj, int32_t y); + +/********************** + * STATIC VARIABLES + **********************/ + +const lv_obj_class_t lv_anim_label_class = { + .constructor_cb = lv_anim_label_constructor, + .instance_size = sizeof(lv_anim_label_t), + .base_class = &lv_obj_class +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t* lv_anim_label_create(lv_obj_t* parent) +{ + lv_obj_t* obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE); + return obj; +} + +void lv_anim_label_set_dir(lv_obj_t* obj, lv_dir_t dir) +{ + lv_anim_label_set_enter_dir(obj, dir); + lv_anim_label_set_exit_dir(obj, dir); +} + +void lv_anim_label_set_enter_dir(lv_obj_t* obj, lv_dir_t dir) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t* alabel = (lv_anim_label_t*)obj; + + alabel->enter_dir = dir; +} + +void lv_anim_label_set_exit_dir(lv_obj_t* obj, lv_dir_t dir) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t* alabel = (lv_anim_label_t*)obj; + + alabel->exit_dir = dir; +} + +void lv_anim_label_set_time(lv_obj_t* obj, uint32_t duration) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t* alabel = (lv_anim_label_t*)obj; + + alabel->duration = duration; +} + +void lv_anim_label_set_path(lv_obj_t* obj, lv_anim_path_cb_t path_cb) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t* alabel = (lv_anim_label_t*)obj; + + alabel->path_cb = path_cb; +} + +void lv_anim_label_add_style(lv_obj_t* obj, lv_style_t* style) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t* alabel = (lv_anim_label_t*)obj; + + lv_obj_add_style(alabel->label_1, style, LV_PART_MAIN); + lv_obj_add_style(alabel->label_2, style, LV_PART_MAIN); +} + +void lv_anim_label_set_custom_enter_anim(lv_obj_t* obj, const lv_anim_t* a) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t* alabel = (lv_anim_label_t*)obj; + + if (a != NULL) { + alabel->a_enter = *a; + } +} + +void lv_anim_label_set_custom_exit_anim(lv_obj_t* obj, const lv_anim_t* a) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t* alabel = (lv_anim_label_t*)obj; + + if (a != NULL) { + alabel->a_exit = *a; + } +} + +static void lv_anim_label_start_anim( + lv_obj_t* label, + lv_dir_t dir, + int32_t start, + int32_t end, + uint32_t duration, + lv_anim_path_cb_t path_cb) +{ + lv_anim_exec_xcb_t exec_xcb; + + if (dir & LV_DIR_HOR) { + exec_xcb = lv_anim_label_set_x; + } else if (dir & LV_DIR_VER) { + exec_xcb = lv_anim_label_set_y; + } else { + return; + } + + lv_anim_t a; + lv_anim_init(&a); + lv_anim_set_var(&a, label); + lv_anim_set_values(&a, start, end); + lv_anim_set_time(&a, duration); + lv_anim_set_path_cb(&a, path_cb); + lv_anim_set_exec_cb(&a, exec_xcb); + lv_anim_start(&a); +} + +void lv_anim_label_push_text(lv_obj_t* obj, const char* txt) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t* alabel = (lv_anim_label_t*)obj; + + lv_obj_t* label_exit = alabel->label_act; + lv_obj_t* label_enter = alabel->label_act == alabel->label_1 ? alabel->label_2 : alabel->label_1; + lv_dir_t enter_dir = alabel->enter_dir; + lv_dir_t exit_dir = alabel->exit_dir; + + lv_label_set_text(label_enter, txt); + lv_obj_update_layout(label_enter); + + lv_coord_t obj_width = lv_obj_get_width(obj); + lv_coord_t obj_height = lv_obj_get_height(obj); + + /* enter */ + lv_coord_t label_enter_width = lv_obj_get_width(label_enter); + lv_coord_t label_enter_height = lv_obj_get_height(label_enter); + lv_coord_t label_enter_end_x = (obj_width - label_enter_width) / 2; + lv_coord_t label_enter_end_y = (obj_height - label_enter_height) / 2; + + if (enter_dir & LV_DIR_HOR) { + lv_obj_set_y(label_enter, label_enter_end_y); + lv_coord_t start_x = label_enter_end_x; + start_x += ((enter_dir == LV_DIR_LEFT) ? -obj_width : obj_width); + lv_anim_label_start_anim( + label_enter, + enter_dir, + start_x, + label_enter_end_x, + alabel->duration, + alabel->path_cb); + } else if (enter_dir & LV_DIR_VER) { + lv_obj_set_x(label_enter, label_enter_end_x); + lv_coord_t start_y = label_enter_end_y; + start_y += ((enter_dir == LV_DIR_TOP) ? -obj_height : obj_height); + lv_anim_label_start_anim( + label_enter, + enter_dir, + start_y, + label_enter_end_y, + alabel->duration, + alabel->path_cb); + } else { + lv_obj_set_pos(label_enter, label_enter_end_x, label_enter_end_y); + } + + if (alabel->a_enter.exec_cb != NULL) { + lv_anim_set_var(&alabel->a_enter, label_enter); + lv_anim_start(&alabel->a_enter); + } + + /* exit */ + lv_coord_t label_exit_width = lv_obj_get_width(label_exit); + lv_coord_t label_exit_height = lv_obj_get_height(label_exit); + lv_coord_t label_exit_start_x = (obj_width - label_exit_width) / 2; + lv_coord_t label_exit_start_y = (obj_height - label_exit_height) / 2; + + if (exit_dir & LV_DIR_HOR) { + lv_obj_set_y(label_exit, label_exit_start_y); + lv_coord_t end_x = label_exit_start_x; + end_x += ((exit_dir == LV_DIR_LEFT) ? obj_width : -obj_width); + lv_anim_label_start_anim( + label_exit, + exit_dir, + label_exit_start_x, + end_x, + alabel->duration, + alabel->path_cb); + } else if (exit_dir & LV_DIR_VER) { + lv_obj_set_x(label_exit, label_exit_start_x); + lv_coord_t end_y = label_exit_start_y; + end_y += ((exit_dir == LV_DIR_TOP) ? obj_height : -obj_height); + lv_anim_label_start_anim( + label_exit, + exit_dir, + label_exit_start_y, + end_y, + alabel->duration, + alabel->path_cb); + } else { + lv_obj_set_pos(label_exit, label_exit_start_x, label_exit_start_y); + } + + if (alabel->a_exit.exec_cb != NULL) { + lv_anim_set_var(&alabel->a_exit, label_exit); + lv_anim_start(&alabel->a_exit); + } + + alabel->label_act = label_enter; +} + +lv_dir_t lv_anim_label_get_enter_dir(lv_obj_t* obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t* alabel = (lv_anim_label_t*)obj; + + return alabel->enter_dir; +} + +lv_dir_t lv_anim_label_get_exit_dir(lv_obj_t* obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t* alabel = (lv_anim_label_t*)obj; + + return alabel->exit_dir; +} + +uint32_t lv_anim_label_get_time(lv_obj_t* obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t* alabel = (lv_anim_label_t*)obj; + + return alabel->duration; +} + +lv_anim_path_cb_t lv_anim_label_get_path(lv_obj_t* obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t* alabel = (lv_anim_label_t*)obj; + + return alabel->path_cb; +} + +const char* lv_anim_label_get_text(lv_obj_t* obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t* alabel = (lv_anim_label_t*)obj; + + return lv_label_get_text(alabel->label_act); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_anim_label_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj) +{ + LV_TRACE_OBJ_CREATE("begin"); + + lv_anim_label_t* alabel = (lv_anim_label_t*)obj; + + alabel->label_1 = lv_label_create(obj); + lv_label_set_text(alabel->label_1, ""); + + alabel->label_2 = lv_label_create(obj); + lv_label_set_text(alabel->label_2, ""); + + alabel->label_act = alabel->label_1; + // alabel->dir = LV_DIR_BOTTOM; + alabel->duration = 500; + alabel->path_cb = lv_anim_path_ease_out; + + lv_anim_init(&alabel->a_enter); + lv_anim_init(&alabel->a_exit); + + LV_TRACE_OBJ_CREATE("finished"); +} + +static void lv_anim_label_set_x(void* obj, int32_t x) +{ + lv_obj_set_x(obj, x); +} + +static void lv_anim_label_set_y(void* obj, int32_t y) +{ + lv_obj_set_y(obj, y); +} \ No newline at end of file diff --git a/bandx/utils/lv_anim_label.h b/bandx/utils/lv_anim_label.h new file mode 100755 index 0000000000000000000000000000000000000000..32b8e85c31f05cc09f01f32a94f2c5f5d6611135 --- /dev/null +++ b/bandx/utils/lv_anim_label.h @@ -0,0 +1,158 @@ +/** + * @file lv_anim_label.h + * + */ + +#ifndef LV_ANIM_LABEL_H +#define LV_ANIM_LABEL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "lvgl/lvgl.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + lv_obj_t obj; + lv_obj_t* label_1; + lv_obj_t* label_2; + lv_obj_t* label_act; + lv_dir_t enter_dir; + lv_dir_t exit_dir; + lv_anim_t a_enter; + lv_anim_t a_exit; + uint32_t duration; + lv_anim_path_cb_t path_cb; +} lv_anim_label_t; + +extern const lv_obj_class_t lv_anim_label_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a new animation label object. + * @param parent Pointer to the parent object. + * @return A pointer to the newly created animation label object. + */ +lv_obj_t* lv_anim_label_create(lv_obj_t* parent); + +/** + * Set the direction of the animation label. + * @param obj Pointer to the animation label object. + * @param dir The direction to set for the animation. + */ +void lv_anim_label_set_dir(lv_obj_t* obj, lv_dir_t dir); + +/** + * Set the entry direction for the animation label. + * @param obj Pointer to the animation label object. + * @param dir The entry direction to set. + */ +void lv_anim_label_set_enter_dir(lv_obj_t* obj, lv_dir_t dir); + +/** + * Set the exit direction for the animation label. + * @param obj Pointer to the animation label object. + * @param dir The exit direction to set. + */ +void lv_anim_label_set_exit_dir(lv_obj_t* obj, lv_dir_t dir); + +/** + * Set the duration of the animation for the animation label. + * @param obj Pointer to the animation label object. + * @param duration The duration of the animation in milliseconds. + */ +void lv_anim_label_set_time(lv_obj_t* obj, uint32_t duration); + +/** + * Set the animation path callback for the animation label. + * @param obj Pointer to the animation label object. + * @param path_cb Callback function for the animation path. + */ +void lv_anim_label_set_path(lv_obj_t* obj, lv_anim_path_cb_t path_cb); + +/** + * Add a style to the animation label. + * @param obj Pointer to the animation label object. + * @param style Pointer to the style to add. + */ +void lv_anim_label_add_style(lv_obj_t* obj, lv_style_t* style); + +/** + * Set a custom enter animation for the animation label. + * @param obj Pointer to the animation label object. + * @param a Pointer to the custom animation. + */ +void lv_anim_label_set_custom_enter_anim(lv_obj_t* obj, const lv_anim_t* a); + +/** + * Set a custom exit animation for the animation label. + * @param obj Pointer to the animation label object. + * @param a Pointer to the custom animation. + */ +void lv_anim_label_set_custom_exit_anim(lv_obj_t* obj, const lv_anim_t* a); + +/** + * Push new text to the animation label. + * @param obj Pointer to the animation label object. + * @param txt The text to push to the animation label. + */ +void lv_anim_label_push_text(lv_obj_t* obj, const char* txt); + +/** + * Get the enter direction of the animation label. + * @param obj Pointer to the animation label object. + * @return The current enter direction of the animation label. + */ +lv_dir_t lv_anim_label_get_enter_dir(lv_obj_t* obj); + +/** + * Get the exit direction of the animation label. + * @param obj Pointer to the animation label object. + * @return The current exit direction of the animation label. + */ +lv_dir_t lv_anim_label_get_exit_dir(lv_obj_t* obj); + +/** + * Get the duration of the animation for the animation label. + * @param obj Pointer to the animation label object. + * @return The duration of the animation in milliseconds. + */ +uint32_t lv_anim_label_get_time(lv_obj_t* obj); + +/** + * Get the animation path callback for the animation label. + * @param obj Pointer to the animation label object. + * @return The callback function for the animation path. + */ +lv_anim_path_cb_t lv_anim_label_get_path(lv_obj_t* obj); + +/** + * Get the current text of the animation label. + * @param obj Pointer to the animation label object. + * @return The current text of the animation label. + */ +const char* lv_anim_label_get_text(lv_obj_t* obj); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_ANIM_LABEL_H*/ \ No newline at end of file diff --git a/bandx/utils/lv_auto_event.c b/bandx/utils/lv_auto_event.c new file mode 100755 index 0000000000000000000000000000000000000000..c99f31eed9ca642e7aa7734059c572fbe1a0c48d --- /dev/null +++ b/bandx/utils/lv_auto_event.c @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_auto_event.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void lv_auto_event_timer_handler(lv_timer_t* timer); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_auto_event_t* lv_auto_event_create(lv_auto_event_data_t* auto_event_data, uint32_t len) +{ + lv_auto_event_t* ae = lv_malloc(sizeof(lv_auto_event_t)); + + if (ae == NULL) { + return NULL; + } + + ae->auto_event_data = auto_event_data; + ae->len = len; + ae->run_index = 0; + ae->timer = lv_timer_create(lv_auto_event_timer_handler, auto_event_data[0].delay, ae); + return ae; +} + +void lv_auto_event_del(lv_auto_event_t* ae) +{ + if (ae == NULL) { + return; + } + + if (ae->timer != NULL) { + lv_timer_del(ae->timer); + ae->timer = NULL; + } + + lv_free(ae); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_auto_event_timer_handler(lv_timer_t* timer) +{ + lv_auto_event_t* ae = lv_timer_get_user_data(timer); + + uint32_t cur_index = ae->run_index; + ae->run_index++; + + if (ae->run_index < ae->len) { + lv_timer_set_period(timer, ae->auto_event_data[ae->run_index].delay); + } else { + lv_timer_del(timer); + ae->timer = NULL; + } + + lv_obj_send_event( + *(ae->auto_event_data[cur_index].obj_p), + ae->auto_event_data[cur_index].event, + ae->auto_event_data[cur_index].user_data); +} \ No newline at end of file diff --git a/bandx/utils/lv_auto_event.h b/bandx/utils/lv_auto_event.h new file mode 100755 index 0000000000000000000000000000000000000000..a101854d1b3c6be8f00935d5cf77e2bed698a2ce --- /dev/null +++ b/bandx/utils/lv_auto_event.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2020 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __LV_AUTO_EVENT_H +#define __LV_AUTO_EVENT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "lvgl/lvgl.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + lv_obj_t** obj_p; + lv_event_code_t event; + uint32_t delay; + void* user_data; +} lv_auto_event_data_t; + +typedef struct { + lv_timer_t* timer; + lv_auto_event_data_t* auto_event_data; + uint32_t len; + uint32_t run_index; +} lv_auto_event_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a new auto event. + * @param auto_event_data Pointer to the data structure containing auto event parameters. + * @param len Length of the auto event data array. + * @return A pointer to the newly created auto event, or NULL if creation failed. + */ +lv_auto_event_t* lv_auto_event_create(lv_auto_event_data_t* auto_event_data, uint32_t len); + +/** + * Delete an existing auto event. + * @param ae Pointer to the auto event to be deleted. + */ +void lv_auto_event_del(lv_auto_event_t* ae); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/bandx/utils/lv_obj_ext_func.c b/bandx/utils/lv_obj_ext_func.c new file mode 100755 index 0000000000000000000000000000000000000000..c20dd33d73b99f60c13bc63705c716c97099b1cd --- /dev/null +++ b/bandx/utils/lv_obj_ext_func.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2020 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_obj_ext_func.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_obj_add_anim( + lv_obj_t* obj, lv_anim_t* a, + lv_anim_exec_xcb_t exec_cb, + int32_t start, int32_t end, + uint16_t time, + uint32_t delay, + lv_anim_ready_cb_t ready_cb, + lv_anim_path_cb_t path_cb) +{ + lv_anim_t anim_temp; + + if (a == NULL) { + a = &anim_temp; + + /* INITIALIZE AN ANIMATION + *-----------------------*/ + lv_anim_init(a); + } + + /* MANDATORY SETTINGS + *------------------*/ + + /*Set the "animator" function*/ + lv_anim_set_exec_cb(a, exec_cb); + + /*Set the "animator" function*/ + lv_anim_set_var(a, obj); + + /*Length of the animation [ms]*/ + lv_anim_set_time(a, time); + + /*Set start and end values. E.g. 0, 150*/ + lv_anim_set_values(a, start, end); + + /* OPTIONAL SETTINGS + *------------------*/ + + /*Time to wait before starting the animation [ms]*/ + lv_anim_set_delay(a, delay); + + /*Set the path in an animation*/ + lv_anim_set_path_cb(a, path_cb); + + /*Set a callback to call when animation is ready.*/ + lv_anim_set_ready_cb(a, ready_cb); + + /*Set a callback to call when animation is started (after delay).*/ + lv_anim_set_start_cb(a, ready_cb); + + /* START THE ANIMATION + *------------------*/ + lv_anim_start(a); /*Start the animation*/ +} + +/********************** + * STATIC FUNCTIONS + **********************/ diff --git a/bandx/utils/lv_obj_ext_func.h b/bandx/utils/lv_obj_ext_func.h new file mode 100755 index 0000000000000000000000000000000000000000..a0f34d76bbb80002e4f801e6ca57135216e2613d --- /dev/null +++ b/bandx/utils/lv_obj_ext_func.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2020 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __LV_OBJ_EXT_FUNC_H +#define __LV_OBJ_EXT_FUNC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "lvgl/lvgl.h" + +/********************* + * DEFINES + *********************/ + +#define LV_ANIM_TIME_DEFAULT 300 +#define LV_ANIM_EXEC(attr) (lv_anim_exec_xcb_t) lv_obj_set_##attr + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Add animation to an object. + * @param obj pointer to the object. + * @param a pointer to the animation (can be NULL). + * @param exec_cb the callback to execute for the animation. + * @param start the starting value of the animation. + * @param end the ending value of the animation. + * @param time the duration of the animation in milliseconds. + * @param delay the delay before the animation starts in milliseconds. + * @param ready_cb the callback to call when the animation is ready (can be NULL). + * @param path_cb the callback for the animation path (can be NULL). + */ +void lv_obj_add_anim( + lv_obj_t* obj, lv_anim_t* a, + lv_anim_exec_xcb_t exec_cb, + int32_t start, + int32_t end, + uint16_t time, + uint32_t delay, + lv_anim_ready_cb_t ready_cb, + lv_anim_path_cb_t path_cb); + +/** + * Macro to add animation to an object with specified attributes. + * @param obj pointer to the object. + * @param attr the attribute to animate. + * @param target the target value for the animation. + * @param time the duration of the animation in milliseconds. + */ +#define LV_OBJ_ADD_ANIM(obj, attr, target, time) \ + do { \ + lv_obj_add_anim( \ + (obj), NULL, \ + (lv_anim_exec_xcb_t)lv_obj_set_##attr, \ + lv_obj_get_##attr(obj), \ + (target), \ + (time), \ + 0, \ + NULL, \ + lv_anim_path_ease_out); \ + } while (0) + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/breakout/.vscode/settings.json b/breakout/.vscode/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..cc695570c08595296c4adc63018dc248a7955059 --- /dev/null +++ b/breakout/.vscode/settings.json @@ -0,0 +1,48 @@ +{ + "files.associations": { + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "limits": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "ranges": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "typeinfo": "cpp", + "map": "cpp" + } +} \ No newline at end of file diff --git a/breakout/Kconfig b/breakout/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..b34c07af636201ea95a17d884665a12be989a5c6 --- /dev/null +++ b/breakout/Kconfig @@ -0,0 +1,17 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config LVX_USE_DEMO_BREAKOUT + bool "breakout" + default n + +if LVX_USE_DEMO_BREAKOUT + config LVX_BREAKOUT_DATA_ROOT + string "BREAKOUT Data Root" + default "/data" + config LVX_BREAKOUT_STACKSIZE + int "LVX_BREAKOUT stack size" + default 65536 +endif diff --git a/breakout/Make.defs b/breakout/Make.defs new file mode 100644 index 0000000000000000000000000000000000000000..e5f6c177df689ddb2c039a5e0b4db1a0082dbce2 --- /dev/null +++ b/breakout/Make.defs @@ -0,0 +1,18 @@ +# +# Copyright (C) 2023 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +ifneq ($(CONFIG_LVX_USE_DEMO_BREAKOUT),) +CONFIGURED_APPS += $(APPDIR)/packages/demos/breakout +endif diff --git a/breakout/Makefile b/breakout/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..f9dbb5020a7bd0dbd8063540a00407edd901c174 --- /dev/null +++ b/breakout/Makefile @@ -0,0 +1,32 @@ +# +# Copyright (C) 2022 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +include $(APPDIR)/Make.defs + +ifeq ($(CONFIG_LVX_USE_DEMO_BREAKOUT), y) +PROGNAME = breakout +PRIORITY = 100 +STACKSIZE = $(CONFIG_LVX_BREAKOUT_STACKSIZE) +MODULE = $(CONFIG_LVX_USE_DEMO_BREAKOUT) +CSRCS += src/Audio/audio_ctl.c +CXXFLAGS += -I$(APPDIR)/packages/demos/breakout/src +CXXEXT := .cpp +CXXSRCS += $(shell find -L ./ -name "*.cpp" | grep -v breakout_main.cpp) + +MAINSRC = breakout_main.cpp +endif + +include $(APPDIR)/Application.mk diff --git a/breakout/Readme.md b/breakout/Readme.md new file mode 100644 index 0000000000000000000000000000000000000000..e83eb2998ad51d90391fb79a46fdcedcf2657b25 --- /dev/null +++ b/breakout/Readme.md @@ -0,0 +1,148 @@ +# 🎮 Breakout Game 《打砖块游戏》 +**“KUN生是旷野,不是轨道!”** +拥有对篮球无限热爱的KUN,在家族中独树一帜,正挥洒着汗水,勤奋努力的击打篮球撞碎砖块,缔造属于自己的篮球哲学。 + +🧱 基于OpenVela和LVGL开发的触屏Breakout打砖块游戏,实现了基本的游戏逻辑,增加了图片素材并实现了打击音效。 + + +在对应素材文件夹下,已通过`readme`和`LICENSE`文件详细标明了作者出处等信息,满足了相应的license协议要求,允许个人在作品及商业中使用,但不得对素材进行单独售卖。 + +以下是第二版游戏界面: + +<img src="./screenshot/screenshot_2.png" alt="" width="75%"> + +# ✨ Features 特点 + +- **游戏区域**:使用 LVGL 创建全屏的游戏区域,并支持动态生成砖块。 +- **挡板与球的物理机制**:挡板通过触摸输入进行控制,球的运动具有物理效果,碰撞后反弹。 +- **砖块系统**:砖块具有血量,可被破坏,支持不同类型砖块的行为。 +- **音效反馈**:根据砖块的血量播放不同的音效。 +- **触摸输入**:支持触摸屏输入来控制挡板移动。 +- **游戏状态管理**:管理游戏状态(例如:PLAYING、GAME_OVER、PAUSE)以控制游戏流程。 + + +# 🖼️ Image Handling(图片处理) +## 1. libpng decoder(libpng 解码器) +好消息,OpenVela系统已经内置了该PNG解码器,仅需将`LIB_PNG`和`LV_USE_LIBPNG`配置为`yes`,即可轻松使用`lv_image_set_src`等函数读取使用`PNG`图片。使用方法可以参考:`https://lvgl.100ask.net/master/details/libs/libpng.html` +此外,这是libpng的github仓库链接:`https://github.com/pnggroup/libpng`,自己手动引入还是有难度。 + +## 2.Image Caching(图片缓存) +LVGL 支持将图像缓存到内存中,以提升图像组件在运行时的加载速度,特别适用于反复绘制的图像场景,如游戏背景、按钮图标等。 +在从外部读入图片过多,会有明显卡顿,可以使用图片缓存的方法,将图片读入内存。具体可以参考:`https://lvgl.100ask.net/master/details/main-components/image.html#overview-image-caching` + +# 🔊 Audio Handling(音频处理) +引用了packages_demos仓库下的music_player的音频控制文件`audio_ctl.c`和`audio_ctl.h`,通过`audio_ctl_init_nxaudio`加载音频文件,`audio_ctl_start`播放音频,`audio_ctl_stop`暂停音频,`audio_ctl_uninit_nxaudio`释放资源。目前实现了碰撞砖块发出音效,但还存在一些bug。 + + + +# 🚀 Getting Started 快速开始 + +首先进入模拟器配置 +## 1.配置模拟器(menuconfig) +```bash +./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap menuconfig +``` + +使用`/`键进入搜索模式 +- 配置`LVX_USE_DEMO_BREAKOUT `为 `yes` +- `LVX_BREAKOUT_DATA_ROOT`的路径设置为`/data ` (Kconfig文件默认预设为 `/data`) +- `LVX_BREAKOUT_STACKSIZE`设置为65536 +- `LIB_PNG`和`LV_USE_LIBPNG`配置为`yes` (读取png图片资源) +- `AUDIO`和`AUDIOUTILS_NXAUDIO_LIB`配置为`yes` (读取wav音频文件) + +## 2.构建和清除构建文件 +### 开始构建 +```bash +./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap -j$(nproc) +``` +### 清理构建产物 +```bash +./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap distclean -j$(nproc) +``` +## 3.ADB推送更新关卡等资源(在模拟器运行状况下) +```bash +adb push apps/packages/demos/breakout/res /data/ +``` + +## 4.启动模拟器 +```bash +./emulator.sh vela +``` + +## 5.启动游戏 +```bash +breakout & +``` +--- +# ▶️ 启动游戏 + +在 main文件函数中初始化 LVGL后,调用: + +ballgame_start(); // 启动游戏 + + +# 🎮 Controls + +- 移动挡板:按住并拖动屏幕,挡板随手指水平移动。 + +- 发射小球:游戏开始时,轻触屏幕即可发射小球。 + +- 重新开始:掉球后点击 "Restart" 按钮重开一局。 + +# 📄 Level File Format (.dat) + +放置关卡文件: +将 .dat 文件放到资源路径下,例如: + + `breakout/res/map/level_1.dat` + +关卡文件为文本格式,使用字符描述砖块: +| 字符 | 说明 | 生命值 | +|------|----------------|--------| +| 空格 | 空(无生成) | 无 | +| `#` | 墙(不可破坏) | -1 | +| `A` | 绿色砖块 | 1 | +| `B` | 橙色砖块 | 2 | +| `C` | 紫色砖块 | 3 | +| `D` | 黄色砖块 | 4 | +| `E` | 蓝色砖块 | 5 | +| 未知类型 | 绿色砖块 | 1 | + +此处读取文件时,会将换行符`\n`,制表符`\r`,文件结尾符号`\0`也识别出来,需要注意排除。 + +关卡示例: +``` +####### +#A B A# +# C # +####### +``` + + +# 素材使用声明 +- 图片素材来源于`https://icon.sucai999.com/`以及`https://craftpix.net` +- 音频素材来源于`https://freesound.org/` + +**本项目中的图片资源可以在个人作品中使用,并且可以用于商业项目,前提是遵守相应的许可协议。** + +- **Microsoft 图片**:可在个人及商业项目中使用,需遵守 **MIT** 许可协议。 +- **Google 图片**:可在个人及商业项目中使用,需遵守 **Apache 2.0** 许可协议,并附带版权声明、许可证文本及修改声明。 +- **CraftPix.net 图片**:可用于个人及商业项目,但需遵守 **CraftPix 自定义许可协议**,保留版权信息并不得单独转售源文件。 +- **freesound音频**:本作品使用的音频文件皆为 **CC0(Creative Commons 0)** 授权的资源,表示该资源已被作者明确放弃所有版权及相关权利,可自由复制、修改、分发,亦可用于个人或商业用途,无需署名、无需许可、无任何限制。 + + + + +# 🛠️ TODO 未来的工作 + +- UI和界面美化 √ + +- 得分与生命系统 + +- 多种道具(如掉落多球分裂,加长板等) + +- 更新多关卡支持与切换 + +- 增加交互音效 √ + +- 更智能的反弹算法(目前在考虑box2d) diff --git a/breakout/breakout_main.cpp b/breakout/breakout_main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6b64e80c7d90d12a4535786598a24671089e944e --- /dev/null +++ b/breakout/breakout_main.cpp @@ -0,0 +1,78 @@ +/**************************************************** + * breakout_main.cpp + * Main program entry for the breakout game on NuttX OS. + ***************************************************/ + +#include <nuttx/config.h> +#include <unistd.h> +#include <uv.h> +#include "src/breakout.h" +#include <lvgl/lvgl.h> + +/** + * A helper function to initialize and run the libuv event loop. + * The loop handles input events, timers, and screen refreshes. + */ +static void lv_nuttx_uv_loop(uv_loop_t* loop, lv_nuttx_result_t* result) +{ + // Create a struct to pass integration info between NuttX and LVGL + lv_nuttx_uv_t uv_info; + void* data; + + // Initialize the libuv event loop + uv_loop_init(loop); + + // Prepare config info to pass to the LVGL-UV integration layer + lv_memset(&uv_info, 0, sizeof(uv_info)); + uv_info.loop = loop; + uv_info.disp = result->disp; // Pass in the initialized display device + uv_info.indev = result->indev; // Pass in the initialized input device (touchscreen) +#ifdef CONFIG_UINPUT_TOUCH + uv_info.uindev = result->utouch_indev; // If configured, also pass virtual user input device +#endif + data = lv_nuttx_uv_init(&uv_info); + uv_run(loop, UV_RUN_DEFAULT); + lv_nuttx_uv_deinit(&data); +} + +/************************************************** + * Application main entry function. + ***************************************************/ +extern "C" int breakout_main(int argc, FAR char *argv[]) +{ + lv_nuttx_dsc_t info; + lv_nuttx_result_t result; + uv_loop_t ui_loop; + lv_memset(&ui_loop, 0, sizeof(uv_loop_t)); + + // Safety check: ensure LVGL is not initialized multiple times + if (lv_is_initialized()) { + LV_LOG_ERROR("LVGL already initialized! aborting."); + return -1; + } + + // Initialize LVGL core library + lv_init(); + + // Initialize and bind NuttX display and input drivers + lv_nuttx_dsc_init(&info); + lv_nuttx_init(&info, &result); + + // Check if the display device was successfully initialized + if (result.disp == NULL) { + LV_LOG_ERROR("lv_demos initialization failure!"); + return 1; + } + + // Call the game start function to create the game UI and all objects + ballgame_start(); + + // Start the main event loop driving the entire UI and application + lv_nuttx_uv_loop(&ui_loop, &result); + + // After the application exits the loop, deinitialize all resources + lv_nuttx_deinit(&result); + lv_deinit(); + + return 0; +} diff --git a/breakout/res/audio/A4.wav b/breakout/res/audio/A4.wav new file mode 100644 index 0000000000000000000000000000000000000000..7137b6c75a10175d9f49910b797be1bbc8bc1632 Binary files /dev/null and b/breakout/res/audio/A4.wav differ diff --git a/breakout/res/audio/B4.wav b/breakout/res/audio/B4.wav new file mode 100644 index 0000000000000000000000000000000000000000..8d2e1e71a6c958ea360daa2645e03ce9b58b52e1 Binary files /dev/null and b/breakout/res/audio/B4.wav differ diff --git a/breakout/res/audio/C4.wav b/breakout/res/audio/C4.wav new file mode 100644 index 0000000000000000000000000000000000000000..2ba65afad1bfd257879b8ed31f5e00e5115613b1 Binary files /dev/null and b/breakout/res/audio/C4.wav differ diff --git a/breakout/res/audio/C5.wav b/breakout/res/audio/C5.wav new file mode 100644 index 0000000000000000000000000000000000000000..1269b363a7fa38d3006d682b5f0b378fc0921dad Binary files /dev/null and b/breakout/res/audio/C5.wav differ diff --git a/breakout/res/audio/D4.wav b/breakout/res/audio/D4.wav new file mode 100644 index 0000000000000000000000000000000000000000..6b18f52995739d98306249d267f957b79d50a1fc Binary files /dev/null and b/breakout/res/audio/D4.wav differ diff --git a/breakout/res/audio/E4.wav b/breakout/res/audio/E4.wav new file mode 100644 index 0000000000000000000000000000000000000000..b9dbeb5d1ce1b1f4b52a8b54e11c5a796c555994 Binary files /dev/null and b/breakout/res/audio/E4.wav differ diff --git a/breakout/res/audio/F4.wav b/breakout/res/audio/F4.wav new file mode 100644 index 0000000000000000000000000000000000000000..fcbd66beb0d0b5e4e4b0c605281d1d781dd2ca1a Binary files /dev/null and b/breakout/res/audio/F4.wav differ diff --git a/breakout/res/audio/G4.wav b/breakout/res/audio/G4.wav new file mode 100644 index 0000000000000000000000000000000000000000..4ffb2b850acb04241d88e42d55dd77816003e253 Binary files /dev/null and b/breakout/res/audio/G4.wav differ diff --git a/breakout/res/audio/LICENSE b/breakout/res/audio/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..a1974cbb9cb88a97653494b3d3f899ee22873653 --- /dev/null +++ b/breakout/res/audio/LICENSE @@ -0,0 +1,36 @@ +CC0 1.0 Universal + +CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. +1. Copyright and Related Rights. + +A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: + + the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; + moral rights retained by the original author(s) and/or performer(s); + publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; + rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; + rights protecting the extraction, dissemination, use and reuse of data in a Work; + database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and + other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. + +To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. +3. Public License Fallback. + +Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. +4. Limitations and Disclaimers. + + No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. + Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. + Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. + Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. + + diff --git a/breakout/res/audio/readme.md b/breakout/res/audio/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..f53e8e758fe1f8bb096b3e037ddc1f92c7b089be --- /dev/null +++ b/breakout/res/audio/readme.md @@ -0,0 +1,24 @@ + +All audio files are from: https://freesound.org/people/Teddy_Frost/ + +All are licensed under the Creative Commons 0 (CC0) license. + +You can copy, modify, distribute, and perform the sound, even for commercial purposes, all without the need of asking permission from the author. + + + + E4.wav by Teddy_Frost — https://freesound.org/s/334542/ — License: Creative Commons 0 + + F4.wav by Teddy_Frost — https://freesound.org/s/334541/ — License: Creative Commons 0 + + G4.wav by Teddy_Frost — https://freesound.org/s/334540/ — License: Creative Commons 0 + + B4.wav by Teddy_Frost — https://freesound.org/s/334539/ — License: Creative Commons 0 + + C4.wav by Teddy_Frost — https://freesound.org/s/334538/ — License: Creative Commons 0 + + C5.wav by Teddy_Frost — https://freesound.org/s/334537/ — License: Creative Commons 0 + + D4.wav by Teddy_Frost — https://freesound.org/s/334536/ — License: Creative Commons 0 + + A4.wav by Teddy_Frost — https://freesound.org/s/334534/ — License: Creative Commons 0 diff --git a/breakout/res/fonts/MiSans-Normal.ttf b/breakout/res/fonts/MiSans-Normal.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3199d464141d0599dd1269a3fef4264a829c6da0 Binary files /dev/null and b/breakout/res/fonts/MiSans-Normal.ttf differ diff --git a/breakout/res/fonts/MiSans-Semibold.ttf b/breakout/res/fonts/MiSans-Semibold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..83c1a532632f582550d48bae117d758191ad1800 Binary files /dev/null and b/breakout/res/fonts/MiSans-Semibold.ttf differ diff --git a/breakout/res/icons/KUN.png b/breakout/res/icons/KUN.png new file mode 100644 index 0000000000000000000000000000000000000000..2efcde2c9a73633e2fefab9d5ab647e03add6b4d Binary files /dev/null and b/breakout/res/icons/KUN.png differ diff --git a/breakout/res/icons/LICENSE b/breakout/res/icons/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..fa127e844fff8bb29492eaf221c04d33c33b660f --- /dev/null +++ b/breakout/res/icons/LICENSE @@ -0,0 +1,53 @@ +# LICENSE + +The copyright and license statements for the image resources used in this project are as follows: + +--- + +## Microsoft Corporation Image Resources (MIT License) + +Images: ball.png, brick_#.png, brick_A.png, brick_B.png, brick_C.png, brick_D.png, brick_E.png, brick_F.png + +Copyright Notice: +Copyright (c) Microsoft Corporation + +License: +MIT License + +Full license text: https://opensource.org/licenses/MIT + +--- + +## Google Inc Image Resources (Apache License 2.0) + +Image: KUN.png + +Copyright Notice: +Copyright 2023 Google Inc + +License: +Apache License, Version 2.0 + +Full license text: https://www.apache.org/licenses/LICENSE-2.0 + +--- + +## CraftPix.net Image Resources (CraftPix.net License) + +Image: background.png + +Copyright Notice: +© CraftPix.net - 2D & 3D Game Assets + +License details: +Please refer to https://craftpix.net/file-licenses/ + +--- + +## Usage Notes + +- The image resources included in this project are subject to the above different licenses; please comply with the corresponding license terms. +- Both MIT and Apache 2.0 licenses allow commercial use but require preserving copyright and license notices. +- Use of CraftPix assets must strictly follow their official license terms; direct resale of source files is prohibited. + + diff --git a/breakout/res/icons/background_1.png b/breakout/res/icons/background_1.png new file mode 100644 index 0000000000000000000000000000000000000000..aa7e66e7c84a48b36da34d9623c852357f4d0a28 Binary files /dev/null and b/breakout/res/icons/background_1.png differ diff --git a/breakout/res/icons/background_2.png b/breakout/res/icons/background_2.png new file mode 100644 index 0000000000000000000000000000000000000000..248c3846b0c644e82e6bc18b47055b3906114602 Binary files /dev/null and b/breakout/res/icons/background_2.png differ diff --git a/breakout/res/icons/background_3.png b/breakout/res/icons/background_3.png new file mode 100644 index 0000000000000000000000000000000000000000..f6bfe203f58483abcc07c249b061b624b93c2c02 Binary files /dev/null and b/breakout/res/icons/background_3.png differ diff --git a/breakout/res/icons/ball.png b/breakout/res/icons/ball.png new file mode 100644 index 0000000000000000000000000000000000000000..4c43f9ea92b4616d9ff331f50d02c4f436384484 Binary files /dev/null and b/breakout/res/icons/ball.png differ diff --git a/breakout/res/icons/brick_#.png b/breakout/res/icons/brick_#.png new file mode 100644 index 0000000000000000000000000000000000000000..280a99c575379949c483db66309a87c86518f0c7 Binary files /dev/null and b/breakout/res/icons/brick_#.png differ diff --git a/breakout/res/icons/brick_A.png b/breakout/res/icons/brick_A.png new file mode 100644 index 0000000000000000000000000000000000000000..db7a95b4af9029dfae6a515545ccd89a38296ceb Binary files /dev/null and b/breakout/res/icons/brick_A.png differ diff --git a/breakout/res/icons/brick_B.png b/breakout/res/icons/brick_B.png new file mode 100644 index 0000000000000000000000000000000000000000..4c6609435fba41c40ed738f060c315d6b9839a9d Binary files /dev/null and b/breakout/res/icons/brick_B.png differ diff --git a/breakout/res/icons/brick_C.png b/breakout/res/icons/brick_C.png new file mode 100644 index 0000000000000000000000000000000000000000..ed398d6f4f84a1731b5a27d843a2da00757b11be Binary files /dev/null and b/breakout/res/icons/brick_C.png differ diff --git a/breakout/res/icons/brick_D.png b/breakout/res/icons/brick_D.png new file mode 100644 index 0000000000000000000000000000000000000000..a77025c84dd17b8e6387d554e89c7518e17e24f8 Binary files /dev/null and b/breakout/res/icons/brick_D.png differ diff --git a/breakout/res/icons/brick_E.png b/breakout/res/icons/brick_E.png new file mode 100644 index 0000000000000000000000000000000000000000..247696da70a88849564a3e5601ed9e64737a1747 Binary files /dev/null and b/breakout/res/icons/brick_E.png differ diff --git a/breakout/res/icons/brick_F.png b/breakout/res/icons/brick_F.png new file mode 100644 index 0000000000000000000000000000000000000000..91f011f2f8ee20c3d0c8133e620761b0319ae326 Binary files /dev/null and b/breakout/res/icons/brick_F.png differ diff --git a/breakout/res/icons/readme.md b/breakout/res/icons/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..5942cc36c39794aefb1d55709b92dbefa1dc3a5b --- /dev/null +++ b/breakout/res/icons/readme.md @@ -0,0 +1,29 @@ +1. Microsoft Images (MIT License) + + Images: ball.png, brick_#.png, brick_A.png, brick_B.png, brick_C.png, brick_D.png, brick_E.png, brick_F.png + + Author: Microsoft Corporation + + License: MIT License + +2. Google Images (Apache 2.0 License) + + Image: KUN.png + + Author: Google Inc + + License: https://www.apache.org/licenses/LICENSE-2.0 + +The Apache 2.0 License requires including copyright notices, the license text, and the NOTICE file (if any), stating any modifications, and allows commercial use. + +3. CraftPix.net Images (CraftPix Custom License) + + Image: background_1.png, bcakground_2.png, bcakground_3.png + + Author: CraftPix.net + + License: https://craftpix.net/file-licenses/ + +The CraftPix license generally requires retaining copyright information, prohibits separate resale of source files, and allows use in commercial projects; please refer to their official license details for specifics. + + diff --git a/breakout/res/map/level_1.dat b/breakout/res/map/level_1.dat new file mode 100644 index 0000000000000000000000000000000000000000..aa63efc2b89f33ddaac25d5c9f1d57568bd44d72 --- /dev/null +++ b/breakout/res/map/level_1.dat @@ -0,0 +1,30 @@ + + + + EEE AEE EEA A + A A EE EE EEE + E E EE EE E + E E EE EE E + E E AE EE EEEE AE EAE E E EEEE E EEEE + E E EE E A A EE E EA AE A A E A EE + E E E E E E E E E E E E E EEE + E E E E EEEEEE E E E E EEEEEE E EEE EE + E E E E E E E EEE E E EE EE + A A EE E E A E E E E A E EE EEE + EEE E EE EEEE EEE EEE A EEEE EEEEE EE EEA + E + EEE + + C C C D C + DDDDDDDDDDD D DDDDDDDD D D DDDDDDDDDD + D D D D D DDDDD D D + D D D D DDDDD D D C D DD D + D D C D D D D D DDDDDDD D D D + DDDDDDDDDDDD D D DDDDD D D D D + D D C D D D DD D D D D + D D D D DDDDD D D D D D D D + D D D D DD D D D DD D + D D D D D D D D D C D C + D D D D D D D D DDCDDDCD DDDDDDDDDDD + DD D DD DD C D C + diff --git a/breakout/res/map/level_2.dat b/breakout/res/map/level_2.dat new file mode 100644 index 0000000000000000000000000000000000000000..ea35758910bdc726383eb7a259fc9681329c7fab --- /dev/null +++ b/breakout/res/map/level_2.dat @@ -0,0 +1,25 @@ + + + AABBBBCCDDAABB DDAABBBBCCDDAA + AABBBBCCDDAABB DDAABBBBCCDDAA + AABBBBCCDDAABB DDAABBBBCCDDAA + + + AABBBBCCDDAABB DDAABBBBCCDDAA + AABBBBCCDDAABB DDAABBBBCCDDAA + AABBBBCCDDAABB DDAABBBBCCDDAA + + + AABBBBCCDDAABB DDAABBBBCCDDAA + AABBBBCCDDAABB DDAABBBBCCDDAA + AABBBBCCDDAABB DDAABBBBCCDDAA + + + AABBBBE ECCDDAA + AABBBBE # # ECCDDAA + AABBBBE # # ECCDDAA + AABBBBE # # ECCDDAA + # # + # # +############### ############## +############### ############## diff --git a/breakout/screenshot/screenshot_1.png b/breakout/screenshot/screenshot_1.png new file mode 100644 index 0000000000000000000000000000000000000000..84845540a78f8984e881e2eb83c0c977e7fab5d0 Binary files /dev/null and b/breakout/screenshot/screenshot_1.png differ diff --git a/breakout/screenshot/screenshot_2.png b/breakout/screenshot/screenshot_2.png new file mode 100644 index 0000000000000000000000000000000000000000..ccea37c533510142d1a8483d72bac88d31d65727 Binary files /dev/null and b/breakout/screenshot/screenshot_2.png differ diff --git a/breakout/src/Audio/audio_ctl.c b/breakout/src/Audio/audio_ctl.c new file mode 100644 index 0000000000000000000000000000000000000000..765c6f437451bc80ac089d6757a758aa82a3edd6 --- /dev/null +++ b/breakout/src/Audio/audio_ctl.c @@ -0,0 +1,287 @@ +/********************* + * INCLUDES + *********************/ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include "audio_ctl.h" + +#include <audioutils/nxaudio.h> + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void app_dequeue_cb(unsigned long arg, + FAR struct ap_buffer_s *apb); +static void app_complete_cb(unsigned long arg); +static void app_user_cb(unsigned long arg, + FAR struct audio_msg_s *msg, FAR bool *running); + +/********************** + * STATIC VARIABLES + **********************/ + +static struct nxaudio_callbacks_s cbs = +{ + app_dequeue_cb, + app_complete_cb, + app_user_cb +}; + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void app_dequeue_cb(unsigned long arg, FAR struct ap_buffer_s *apb) +{ + FAR audioctl_s *ctl = (FAR audioctl_s *)(uintptr_t)arg; + + if (!apb) + { + return; + } + + if (ctl->seek) { + lseek(ctl->fd, ctl->seek_position, SEEK_SET); + ctl->file_position = ctl->seek_position; + ctl->seek = false; + } + + apb->nbytes = read(ctl->fd, apb->samp, apb->nmaxbytes); + apb->curbyte = 0; + apb->flags = 0; + + while (0 < apb->nbytes && apb->nbytes < apb->nmaxbytes) + { + int n = apb->nmaxbytes - apb->nbytes; + int ret = read(ctl->fd, &apb->samp[apb->nbytes], n); + + if (0 >= ret) + { + break; + } + apb->nbytes += ret; + } + + if (apb->nbytes < apb->nmaxbytes) + { + close(ctl->fd); + ctl->fd = -1; + + return ; + } + + ctl->file_position += apb->nbytes; + + nxaudio_enqbuffer(&ctl->nxaudio, apb); +} + +static void app_complete_cb(unsigned long arg) +{ + /* Do nothing.. */ + + printf("Audio loop is Done\n"); +} + +static void app_user_cb(unsigned long arg, + FAR struct audio_msg_s *msg, FAR bool *running) +{ + /* Do nothing.. */ +} + +static FAR void *audio_loop_thread(pthread_addr_t arg) +{ + FAR audioctl_s *ctl = (FAR audioctl_s *)arg; + + nxaudio_start(&ctl->nxaudio); + nxaudio_msgloop(&ctl->nxaudio, &cbs, + (unsigned long)(uintptr_t)ctl); + + return NULL; +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +FAR audioctl_s *audio_ctl_init_nxaudio(FAR const char *arg) +{ + FAR audioctl_s *ctl; + int ret; + int i; + + ctl = (FAR audioctl_s *)malloc(sizeof(audioctl_s)); + if(ctl == NULL) + { + return NULL; + } + + ctl->seek = false; + ctl->seek_position = 0; + ctl->file_position = 0; + + ctl->fd = open(arg, O_RDONLY); + if (ctl->fd < 0) { + printf("can't open audio file\n"); + return NULL; + } + + read(ctl->fd, &ctl->wav, sizeof(ctl->wav)); + + ret = init_nxaudio(&ctl->nxaudio, ctl->wav.fmt.samplerate, + ctl->wav.fmt.bitspersample, + ctl->wav.fmt.numchannels); + if (ret < 0) + { + printf("init_nxaudio() return with error!!\n"); + return NULL; + } + + for (i = 0; i < ctl->nxaudio.abufnum; i++) + { + app_dequeue_cb((unsigned long)ctl, ctl->nxaudio.abufs[i]); + } + + ctl->state = AUDIO_CTL_STATE_INIT; + + return ctl; +} + +int audio_ctl_start(FAR audioctl_s *ctl) +{ + if (ctl == NULL) + return -EINVAL; + + if (ctl->state != AUDIO_CTL_STATE_INIT && ctl->state != AUDIO_CTL_STATE_PAUSE) + { + return -1; + } + + ctl->state = AUDIO_CTL_STATE_START; + + pthread_attr_t tattr; + struct sched_param sparam; + + pthread_attr_init(&tattr); + sparam.sched_priority = sched_get_priority_max(SCHED_FIFO) - 9; + pthread_attr_setschedparam(&tattr, &sparam); + pthread_attr_setstacksize(&tattr, 4096); + + pthread_create(&ctl->pid, &tattr, audio_loop_thread, + (pthread_addr_t)ctl); + + pthread_attr_destroy(&tattr); + pthread_setname_np(ctl->pid, "audioctl_thread"); + + return 0; +} + +int audio_ctl_pause(FAR audioctl_s *ctl) +{ + if (ctl == NULL) + return -EINVAL; + + if (ctl->state != AUDIO_CTL_STATE_START) + { + return -1; + } + + ctl->state = AUDIO_CTL_STATE_PAUSE; + + return nxaudio_pause(&ctl->nxaudio); +} + +int audio_ctl_resume(FAR audioctl_s *ctl) +{ + if (ctl == NULL) + return -EINVAL; + + if (ctl->state != AUDIO_CTL_STATE_PAUSE) + { + return -1; + } + + ctl->state = AUDIO_CTL_STATE_START; + + return nxaudio_resume(&ctl->nxaudio); +} + +int audio_ctl_seek(FAR audioctl_s *ctl, unsigned ms) +{ + if (ctl == NULL) + return -EINVAL; + + ctl->seek_position = ms * ctl->wav.fmt.samplerate * ctl->wav.fmt.bitspersample * ctl->wav.fmt.numchannels / 8; + ctl->seek = true; + + return 0; +} + +int audio_ctl_stop(FAR audioctl_s *ctl) +{ + if (ctl == NULL) + return -EINVAL; + + if (ctl->state != AUDIO_CTL_STATE_PAUSE && ctl->state != AUDIO_CTL_STATE_START) + { + return -1; + } + + ctl->state = AUDIO_CTL_STATE_STOP; + + nxaudio_stop(&ctl->nxaudio); + + if (ctl->pid > 0) + { + pthread_join(ctl->pid, NULL); + } + + return 0; +} + +int audio_ctl_set_volume(FAR audioctl_s *ctl, uint16_t vol) +{ + if (ctl == NULL) + return -EINVAL; + + return nxaudio_setvolume(&ctl->nxaudio, vol); +} + +int audio_ctl_get_position(FAR audioctl_s *ctl) +{ + if (ctl == NULL) + return -EINVAL; + + return ctl->file_position / (ctl->wav.fmt.bitspersample * ctl->wav.fmt.numchannels * ctl->wav.fmt.samplerate / 8); +} + +int audio_ctl_uninit_nxaudio(FAR audioctl_s *ctl) +{ + if (ctl == NULL) + return -EINVAL; + + if (ctl->state == AUDIO_CTL_STATE_NOP) + { + return 0; + } + + if (ctl->fd > 0) + { + close(ctl->fd); + ctl->fd = -1; + } + + fin_nxaudio(&ctl->nxaudio); + + free(ctl); + + return 0; +} diff --git a/breakout/src/Audio/audio_ctl.h b/breakout/src/Audio/audio_ctl.h new file mode 100644 index 0000000000000000000000000000000000000000..e7e60993b3da128a7917731c5f59195a718653f2 --- /dev/null +++ b/breakout/src/Audio/audio_ctl.h @@ -0,0 +1,85 @@ +#ifndef AUDIO_CTL_H +#define AUDIO_CTL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include <audioutils/nxaudio.h> +#include <pthread.h> + +enum { + AUDIO_CTL_STATE_NOP, + AUDIO_CTL_STATE_INIT, + AUDIO_CTL_STATE_START, + AUDIO_CTL_STATE_PAUSE, + AUDIO_CTL_STATE_STOP, +}; + +typedef struct wav_riff { + /* chunk "riff" */ + char chunkID[4]; /* "RIFF" */ + /* sub-chunk-size */ + uint32_t chunksize; /* 36 + subchunk2size */ + /* sub-chunk-data */ + char format[4]; /* "WAVE" */ +} riff_s; + +typedef struct wav_fmt { + /* sub-chunk "fmt" */ + char subchunk1ID[4]; /* "fmt " */ + /* sub-chunk-size */ + uint32_t subchunk1size; /* 16 for PCM */ + /* sub-chunk-data */ + uint16_t audioformat; /* PCM = 1*/ + uint16_t numchannels; /* Mono = 1, Stereo = 2, etc. */ + uint32_t samplerate; /* 8000, 44100, etc. */ + uint32_t byterate; /* = samplerate * numchannels * bitspersample/8 */ + uint16_t blockalign; /* = numchannels * bitspersample/8 */ + uint16_t bitspersample; /* 8bits, 16bits, etc. */ +} fmt_s; + +typedef struct wav_data { + /* sub-chunk "data" */ + char subchunk2ID[4]; /* "data" */ + /* sub-chunk-size */ + uint32_t subchunk2size; /* data size */ + /* sub-chunk-data */ + //Data_block_t block; +} data_s; + +typedef struct wav_fotmat { + riff_s riff; + fmt_s fmt; + data_s data; +} wav_s; + +typedef struct audioctl { + struct nxaudio_s nxaudio; + wav_s wav; + int fd; + int state; + pthread_t pid; + int seek; + uint32_t seek_position; + uint32_t file_position; +} audioctl_s; + +FAR audioctl_s *audio_ctl_init_nxaudio(FAR const char *arg); +int audio_ctl_start(FAR audioctl_s *ctl); +int audio_ctl_pause(FAR audioctl_s *ctl); +int audio_ctl_resume(FAR audioctl_s *ctl); +int audio_ctl_seek(FAR audioctl_s *ctl, unsigned ms); +int audio_ctl_stop(FAR audioctl_s *ctl); +int audio_ctl_set_volume(FAR audioctl_s *ctl, uint16_t vol); +int audio_ctl_get_position(FAR audioctl_s *ctl); +int audio_ctl_uninit_nxaudio(FAR audioctl_s *ctl); + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /* AUDIO_CTL_H */ diff --git a/breakout/src/Ball/Ball.cpp b/breakout/src/Ball/Ball.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3f8d393b6b8e8d0068d019956e39927d346a9a05 --- /dev/null +++ b/breakout/src/Ball/Ball.cpp @@ -0,0 +1,115 @@ +#include "Ball.h" +#include "GameResourceManager/GameResourceManager.h" +#include <cmath> + +// Define gravity +static const float GRAVITY = 350.0f; +// Define the maximum ball speed +static const float MAX_BALL_SPEED = 750.0f; + +Ball::Ball(lv_obj_t* parent, float radius) : m_state(State::HELD), m_radius(radius), m_gui_object(nullptr) { + GameResourceManager resourceManager; + auto ball_src = resourceManager.getIconSource("ball.png"); + + printf("DEBUG: ball image loaded and decoded to RGB565 for the first time.\n"); + + m_gui_object = lv_img_create(parent); + if (!m_gui_object) { + printf("Failed to create lv_img object\n"); + return; + } + if(ball_src) { + lv_img_set_src(m_gui_object, ball_src); + } + else{ + printf("Failed to load ball.png\n"); + } + lv_obj_set_size(m_gui_object, m_radius * 2, m_radius * 2); + lv_style_init(&m_style); + lv_style_set_bg_color(&m_style, lv_color_white()); + lv_style_set_radius(&m_style, LV_RADIUS_CIRCLE); + lv_style_set_bg_color(&m_style, lv_palette_main(LV_PALETTE_RED)); + lv_style_set_border_width(&m_style, 0); + lv_obj_add_style(m_gui_object, &m_style, 0); +} + +void Ball::update(float deltaTime) { + if (m_state != State::MOVING) { + return; + } + + // Apply gravity + m_vel.y += GRAVITY * deltaTime; + + // Calculate current speed squared + float speed_sq = m_vel.x * m_vel.x + m_vel.y * m_vel.y; + float max_speed_sq = MAX_BALL_SPEED * MAX_BALL_SPEED; + + // If current speed squared exceeds max speed squared + if (speed_sq > max_speed_sq) { + // Calculate actual current speed + float current_speed = sqrtf(speed_sq); + // Scale velocity vector proportionally to max speed + m_vel.x = (m_vel.x / current_speed) * MAX_BALL_SPEED; + m_vel.y = (m_vel.y / current_speed) * MAX_BALL_SPEED; + } + + // Update position based on the finalized velocity + m_pos.x += m_vel.x * deltaTime; + m_pos.y += m_vel.y * deltaTime; + + // Update the LVGL object's position on screen + lv_obj_set_pos(m_gui_object, (lv_coord_t)(m_pos.x - m_radius), (lv_coord_t)(m_pos.y - m_radius)); +} + +void Ball::stickToPaddle(const Rect& paddleRect) { + if (m_state != State::HELD) return; + m_pos.x = paddleRect.x + paddleRect.width / 2.0f; + m_pos.y = paddleRect.y - m_radius; + lv_obj_set_pos(m_gui_object, (lv_coord_t)(m_pos.x - m_radius), (lv_coord_t)(m_pos.y - m_radius)); +} + +void Ball::launch() { + if (m_state == State::HELD) { + m_state = State::MOVING; + m_vel = { 150.0f, -750.0f }; + } +} + +void Ball::bounceX() { + m_vel.x = -m_vel.x; +} + +void Ball::bounceY() { + m_vel.y = -m_vel.y; +} + +void Ball::boostSpeed(float factor) { + m_vel.x *= factor; + m_vel.y *= factor; +} + +void Ball::setPosition(const Vec2& pos) { + m_pos = pos; + lv_obj_set_pos(m_gui_object, (lv_coord_t)(m_pos.x - m_radius), (lv_coord_t)(m_pos.y - m_radius)); +} + +void Ball::setVelocity(const Vec2& vel) { + m_vel = vel; +} + +Ball::State Ball::getState() const { + return m_state; +} + +Vec2 Ball::getPosition() const { + return m_pos; +} + +Vec2 Ball::getVelocity() const { + return m_vel; +} + +float Ball::getRadius() const { + return m_radius; +} \ No newline at end of file diff --git a/breakout/src/Ball/Ball.h b/breakout/src/Ball/Ball.h new file mode 100644 index 0000000000000000000000000000000000000000..fdd6557722e30de69de4d5afb546cac9b67332d8 --- /dev/null +++ b/breakout/src/Ball/Ball.h @@ -0,0 +1,34 @@ +#ifndef BALL_H +#define BALL_H + +#pragma once + +#include "breakout_types.h" + + + +class Ball { +public: + enum class State { HELD, MOVING }; + Ball(lv_obj_t* parent, float radius); + void update(float deltaTime); + void stickToPaddle(const Rect& paddleRect); + void launch(); + void bounceX(); + void bounceY(); + void setPosition(const Vec2& pos); + void setVelocity(const Vec2& vel); + void boostSpeed(float factor); + Vec2 getVelocity() const; + State getState() const; + Vec2 getPosition() const; + float getRadius() const; +private: + State m_state; + Vec2 m_pos, m_vel; + float m_radius; + lv_obj_t* m_gui_object; + lv_style_t m_style; +}; + +#endif // BALL_H \ No newline at end of file diff --git a/breakout/src/Brick/Brick.cpp b/breakout/src/Brick/Brick.cpp new file mode 100644 index 0000000000000000000000000000000000000000..43a4311181ed12c4960786067e2783abc1f4acda --- /dev/null +++ b/breakout/src/Brick/Brick.cpp @@ -0,0 +1,128 @@ +/******************************************** + * Brick.cpp + * Implementation file for the Brick class + * Responsible for creating individual bricks, managing hit points, updating colors, and handling collisions. + *******************************************/ +#include "GameResourceManager/GameResourceManager.h" +#include "Brick.h" + +/***************************************** + * x Brick's X coordinate + * y Brick's Y coordinate + * width Brick's width (also used for height as bricks are square) + * type Character representing the brick type and its initial hit points + ****************************************/ +Brick::Brick(lv_obj_t* parent, float x, float y, float width, char type) + : m_is_active(true), + m_rect{x, y, width, width}, + m_gui_object(nullptr) +{ + // Set hit points based on brick type character + switch(type) { + case '#': m_hp = -1; break; // Indestructible wall + case 'A': m_hp = 1; break; + case 'B': m_hp = 2; break; + case 'C': m_hp = 3; break; + case 'D': m_hp = 4; break; + case 'E': m_hp = 5; break; + default: m_hp = 1; break; + } + + m_gui_object = lv_img_create(parent); + if (!m_gui_object) { + printf("Failed to create lv_img object\n"); + return; + } + updateImage(); + // Set position and size + lv_obj_set_pos(m_gui_object, x, y); + lv_obj_set_size(m_gui_object, width, width); + + // Add black border style + static lv_style_t border_style; + + lv_style_init(&border_style); + lv_style_set_bg_color(&border_style, lv_color_white()); + lv_style_set_border_width(&border_style, 1); + lv_style_set_border_color(&border_style, lv_color_black()); + lv_style_set_pad_all(&border_style, 0); + lv_obj_add_style(m_gui_object, &border_style, 0); +} + +/*************************************** + * Destructor automatically called when Brick object is deleted + * Ensures corresponding UI object is also removed from screen to prevent memory leaks. + ***************************************/ +Brick::~Brick() { + if (m_gui_object) { + lv_obj_del(m_gui_object); + m_gui_object = nullptr; + } +} + +/** + * Called when the ball hits the brick + */ +void Brick::onHit() { + // Only apply damage if hit points > 0 (i.e., not an indestructible wall) + if (m_hp > 0) { + m_hp--; // Decrease hit points by 1 + + if (m_hp == 0) { + // If hit points are depleted + m_is_active = false; // Mark brick as inactive, collision checks will ignore it + lv_obj_add_flag(m_gui_object, LV_OBJ_FLAG_HIDDEN); // Hide brick's UI object on screen + } else { + // If still has hit points, update color for visual feedback + updateImage(); + } + } +} + +/** + * Checks if the brick is currently active (not destroyed) + * Returns true if active, false if destroyed + */ +bool Brick::isActive() const { + return m_is_active; +} + +/** + * Gets the bounding box of the brick (collision box) + * Returns a Rect containing position and size + */ +Rect Brick::getBoundingBox() const { + return m_rect; +} + +int Brick::getHP() const { + return m_hp; +} + +/** + * Updates the brick's background image based on current hit points + */ +void Brick::updateImage() { + if (!m_gui_object) return; + + std::string image_filename; + switch (m_hp) { + case -1: image_filename = "brick_#.png"; break; // Wall: black + case 1: image_filename = "brick_A.png"; break; // HP=1: green + case 2: image_filename = "brick_B.png"; break; // HP=2: orange + case 3: image_filename = "brick_C.png"; break; // HP=3: purple + case 4: image_filename = "brick_D.png"; break; // HP=6: yellow + case 5: image_filename = "brick_E.png"; break; // HP=5: blue + default: image_filename = "brick_F.png"; break; // Damaged (blue-green) + } + + GameResourceManager resourceManager; + auto img_src = resourceManager.getIconSource(image_filename); + if(img_src) { + lv_img_set_src(m_gui_object, img_src); + } + else{ + printf("Failed to load brick.png\n"); + } + +} diff --git a/breakout/src/Brick/Brick.h b/breakout/src/Brick/Brick.h new file mode 100644 index 0000000000000000000000000000000000000000..bd9df2b1072d3b8f899032f5e4980dc8e17e2e3c --- /dev/null +++ b/breakout/src/Brick/Brick.h @@ -0,0 +1,23 @@ +#ifndef BRICK_H +#define BRICK_H + +#pragma once +#include "breakout_types.h" + +class Brick { +public: + Brick(lv_obj_t* parent, float x, float y, float width, char type); + ~Brick(); + void onHit(); + bool isActive() const; + Rect getBoundingBox() const; + int getHP() const; +private: + void updateImage(); + int m_hp; + bool m_is_active; + Rect m_rect; + lv_obj_t* m_gui_object; + lv_style_t m_style; +}; +#endif // BRICK_H \ No newline at end of file diff --git a/breakout/src/GameResourceManager/GameResourceManager.cpp b/breakout/src/GameResourceManager/GameResourceManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5227174c7e777288c5521e6a3b4df9092ae0b04a --- /dev/null +++ b/breakout/src/GameResourceManager/GameResourceManager.cpp @@ -0,0 +1,221 @@ +/** + * GameResourceManager.cpp + * Implementation file for the game resource manager. + * Responsible for constructing resource paths, loading level maps, + * and managing the image resource cache. + * (Version: uses lv_draw_buf_dup and lv_draw_buf_destroy) + */ + +#include "GameResourceManager.h" +#include <cstdio> +#include "Brick/Brick.h" +#include "breakout.h" + +// Define the static member variable for image caching, now storing lv_draw_buf_t* +std::map<std::string, lv_draw_buf_t*> GameResourceManager::s_imageCache; + +GameResourceManager::GameResourceManager() : m_audioCtl(nullptr) {} +GameResourceManager::~GameResourceManager() { + stopAudio(); +} + +/** + * @brief Frees all cached image resources. + * Equivalent to the `cleanup_resources` function from your example. + */ +void GameResourceManager::cleanupCache() { + printf("[ResourceManager] Cleaning up image cache, %zu items to delete.\n", s_imageCache.size()); + for (auto const& [key, buf] : s_imageCache) { + if (buf != nullptr) { + lv_draw_buf_destroy(buf); // Use LVGL function to destroy draw_buf + } + } + s_imageCache.clear(); + printf("[ResourceManager] Cache cleanup complete.\n"); +} + +/** + * @brief Retrieves an icon resource. Loads and caches it if not already cached. + * Core logic corresponding to your `app_create_main_page` example. + */ +const lv_draw_buf_t* GameResourceManager::getIconSource(const std::string& iconName) { + // 1. Check cache + auto it = s_imageCache.find(iconName); + if (it != s_imageCache.end()) { + return it->second; + } + + // 2. Load and decode + std::string fullPath = getIconsPath() + "/" + iconName; + + lv_image_decoder_dsc_t decoder_dsc; + lv_result_t res = lv_image_decoder_open(&decoder_dsc, fullPath.c_str(), NULL); + + if (res != LV_RESULT_OK) { + printf("[ERROR] Failed to open image decoder: %s\n", fullPath.c_str()); + return nullptr; + } + + // 3. Copy the decoded image data to a permanent draw_buf + lv_draw_buf_t* permanent_buf = lv_draw_buf_dup(decoder_dsc.decoded); + + // 4. Close decoder and free temporary resources + lv_image_decoder_close(&decoder_dsc); + + if (permanent_buf == nullptr) { + printf("[ERROR] Failed to duplicate draw buffer.\n"); + return nullptr; + } + + // 5. Cache and return + s_imageCache[iconName] = permanent_buf; + printf("[ResourceManager] New image cached: %s\n", iconName.c_str()); + + return permanent_buf; +} + +// --- Resource path accessors --- + +std::string GameResourceManager::getResPath() { + return std::string(RES_ROOT) + "/res"; +} + +std::string GameResourceManager::getMapPath() { + return getResPath() + "/map"; +} + +std::string GameResourceManager::getFontsPath() { + return getResPath() + "/fonts"; +} + +std::string GameResourceManager::getIconsPath() { + return getResPath() + "/icons"; +} + +std::string GameResourceManager::getAudioPath() { + return getResPath() + "/audio"; +} + +/** + * @brief Loads a map from file and populates the game brick list. + */ +bool GameResourceManager::loadMap(const std::string& mapName, Game* game) { + std::string fullPath = getMapPath() + "/" + mapName; + const int BRICK_COLS = 80; + const int BRICK_ROWS = 35; + const float BRICK_WIDTH = (float)GAME_AREA_WIDTH / BRICK_COLS; + FILE* file = fopen(fullPath.c_str(), "r"); + if (!file) { + return false; + } + char line[BRICK_COLS + 2]; + for (int row = 0; row < BRICK_ROWS; ++row) { + if (!fgets(line, sizeof(line), file)) { + break; + } + for (int col = 0; col < BRICK_COLS && line[col] != '\0'; ++col) { + char brickType = line[col]; + if (brickType != ' ' && brickType != '\n' && brickType != '\r') { + float x = col * BRICK_WIDTH; + float y = row * BRICK_WIDTH; + Brick* newBrick = new Brick(game->m_game_area, x, y, BRICK_WIDTH, brickType); + game->m_bricks.push_back(newBrick); + } + } + } + fclose(file); + return true; +} + +/** + * @brief Loads and displays a background image onto the parent object. + */ +void GameResourceManager::loadBackground(lv_obj_t* parent, const std::string& iconName) { + const lv_draw_buf_t* bg_src = getIconSource(iconName); + if (!bg_src) return; + + lv_obj_t* bg_img = lv_img_create(parent); + lv_img_set_src(bg_img, bg_src); // Use draw buffer directly + lv_obj_set_size(bg_img, GAME_AREA_WIDTH, GAME_AREA_HEIGHT); + lv_obj_align(bg_img, LV_ALIGN_TOP_LEFT, 0, 0); + lv_obj_move_background(bg_img); +} + +/** + * @brief Plays the corresponding audio based on the brick HP. + * + * This function selects the appropriate `.wav` file based on the brick's HP, + * initializes or reinitializes the audio controller, and starts audio playback. + * If the current audio is already playing for the given brick HP, it avoids reinitialization. + * + * @param brickHp The HP of the brick, used to select the correct audio file. + * @return true if the audio started successfully, false if an error occurred. + */ +bool GameResourceManager::playAudio(int brickHp) { + const char* wavFile = nullptr; + // Select appropriate audio file based on brick's HP + switch (brickHp) { + case -1: wavFile = "C4.wav"; break; // For brick HP -1, play C4.wav + case 1: wavFile = "E4.wav"; break; // For brick HP 1, play E4.wav + case 2: wavFile = "F4.wav"; break; // For brick HP 2, play F4.wav + case 3: wavFile = "G4.wav"; break; // For brick HP 3, play G4.wav + case 4: wavFile = "A4.wav"; break; // For brick HP 4, play A4.wav + case 5: wavFile = "B4.wav"; break; // For brick HP 5, play B4.wav + case 6: wavFile = "C5.wav"; break; // For brick HP 6, play C5.wav + default: wavFile = "C4.wav"; break; // Default case, play C4.wav + } + + // Build full audio file path + std::string fullPath = getAudioPath() + "/" + wavFile; + + if (!m_audioCtl) { + // If no audio control is initialized, initialize the audio controller + m_audioCtl = audio_ctl_init_nxaudio(fullPath.c_str()); + if (!m_audioCtl) { + //printf("[Audio] Failed to initialize audio: %s\n", fullPath.c_str()); + return false; + } + } else { + //printf("[Audio] Stopping and reinitializing audio controller...\n"); + audio_ctl_stop(m_audioCtl); // Stop the current audio + audio_ctl_uninit_nxaudio(m_audioCtl); // Uninitialize the current audio controller + + // Reinitialize the audio controller with the new file + m_audioCtl = audio_ctl_init_nxaudio(fullPath.c_str()); + if (!m_audioCtl) { + //printf("[Audio] Failed to reinitialize audio: %s\n", fullPath.c_str()); + return false; + } + } + + if (m_audioCtl) { + audio_ctl_start(m_audioCtl); // Start the audio playback + //printf("[Audio] Playing: %s\n", fullPath.c_str()); + return true; + } + + return false; +} +/** + * @brief Stops the currently playing audio and releases resources. + * + * This function stops the audio if it is currently playing, uninitializes the + * audio controller, and frees associated resources. + */ +void GameResourceManager::stopAudio() { + // Check if the audio controller is initialized + if (m_audioCtl != nullptr) { + //printf("[Audio] Stopping and uninitializing audio controller...\n"); + + // If audio is playing, stop it + audio_ctl_stop(m_audioCtl); + + // Uninitialize the audio controller and clean up + audio_ctl_uninit_nxaudio(m_audioCtl); + m_audioCtl = nullptr; + + //printf("[Audio] Audio stopped and cleaned up.\n"); + } else { + //printf("[Audio] No audio to stop.\n"); + } +} diff --git a/breakout/src/GameResourceManager/GameResourceManager.h b/breakout/src/GameResourceManager/GameResourceManager.h new file mode 100644 index 0000000000000000000000000000000000000000..07a6ff5b3194a6ea0c9c0f929c99e56779832184 --- /dev/null +++ b/breakout/src/GameResourceManager/GameResourceManager.h @@ -0,0 +1,39 @@ +#ifndef GAME_RESOURCE_MANAGER_H +#define GAME_RESOURCE_MANAGER_H + +#include <string> +#include <map> +#include "lvgl.h" +#include "Audio/audio_ctl.h" +#define RES_ROOT CONFIG_LVX_BREAKOUT_DATA_ROOT + +class Game; + +class GameResourceManager { +public: + GameResourceManager(); + ~GameResourceManager(); + + std::string getResPath(); + std::string getMapPath(); + std::string getFontsPath(); + std::string getIconsPath(); + std::string getAudioPath(); + + bool playAudio(int brickHp); + void stopAudio(); + + bool loadMap(const std::string& mapName, Game* game); + void loadBackground(lv_obj_t* parent, const std::string& iconName); + + const lv_draw_buf_t* getIconSource(const std::string& iconName); + + static void cleanupCache(); + +private: + + static std::map<std::string, lv_draw_buf_t*> s_imageCache; + audioctl_s* m_audioCtl = nullptr; +}; + +#endif // GAME_RESOURCE_MANAGER_H \ No newline at end of file diff --git a/breakout/src/Paddle/Paddle.h b/breakout/src/Paddle/Paddle.h new file mode 100644 index 0000000000000000000000000000000000000000..9c192c2cafbd1392091d681d8f5b4bcdc294f880 --- /dev/null +++ b/breakout/src/Paddle/Paddle.h @@ -0,0 +1,34 @@ +#ifndef PADDLE_H +#define PADDLE_H + +#include "lvgl.h" + +#include "breakout_types.h" +class Paddle { +public: + // 构造函数 + Paddle(lv_obj_t* parent, float startX, float startY, float width, float height, Rect screenBounds); + + // 析构函数 + ~Paddle(); + + // 关键修复:添加之前缺失的所有成员函数的声明 + void update(float deltaTime); + Rect getBoundingBox() const; + void moveTo(float targetX); + void stopMovement(); + float getX() const; + float getY() const; + +private: + float m_x; + float m_y; + float m_width; + float m_height; + float m_targetX; + bool m_isMoving; + Rect m_screenBounds; + lv_obj_t* m_gui_object; // 指向LVGL图片对象的指针 +}; + +#endif // PADDLE_H diff --git a/breakout/src/Paddle/paddle.cpp b/breakout/src/Paddle/paddle.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f5e83f8a1d6a232bd4cc2015e4c6a2a251125faf --- /dev/null +++ b/breakout/src/Paddle/paddle.cpp @@ -0,0 +1,98 @@ +/************************************** + * Paddle.cpp + * Implementation file for the player-controlled paddle class + * Responsible for paddle creation, updating, movement, and boundary checking logic. + *************************************/ +#include "Paddle.h" +#include "GameResourceManager/GameResourceManager.h" +#include <algorithm> +#include <cmath> +#include <cstdio> + + + +Paddle::Paddle(lv_obj_t* parent, float startX, float startY, float width, float height, Rect screenBounds) + : m_x(startX), + m_y(startY), + m_width(width), + m_height(height), + m_targetX(0.0f), + m_isMoving(false), + m_screenBounds(screenBounds), + m_gui_object(nullptr) +{ + printf("DEBUG: Paddle constructor called.\n"); + + // Image data and descriptor must remain static to ensure their lifetime + + GameResourceManager resourceManager; + auto paddle_src = resourceManager.getIconSource("KUN.png"); + + printf("DEBUG: Paddle image requested from cache/file.\n"); + + m_gui_object = lv_img_create(parent); + if (!m_gui_object) { + printf("Failed to create lv_img object\n"); + return; + } + + if(paddle_src) { + lv_img_set_src(m_gui_object, paddle_src); + } + else{ + printf("Failed to load KUN.png\n"); + } + lv_obj_set_size(m_gui_object, (lv_coord_t)m_width, (lv_coord_t)m_height); + lv_obj_set_pos(m_gui_object, (lv_coord_t)m_x, (lv_coord_t)m_y); + + m_targetX = m_x + m_width / 2.0f; +} + +Paddle::~Paddle() { + printf("DEBUG: Paddle destructor called. Deleting lv_obj...\n"); + if (m_gui_object) { + lv_obj_del(m_gui_object); + m_gui_object = nullptr; + } +} + +void Paddle::update(float deltaTime) { + if (m_isMoving) { + float diff = m_targetX - (m_x + m_width / 2.0f); + float speed = 700.0f; // Movement speed, can be adjusted as needed + if (std::abs(diff) > 1.0f) { + float move = speed * deltaTime; + if (diff > 0) { + m_x += std::min(move, diff); + } else { + m_x += std::max(-move, diff); + } + + // Boundary check + m_x = std::max(m_screenBounds.x, std::min(m_x, m_screenBounds.x + m_screenBounds.width - m_width)); + + if (m_gui_object) { + lv_obj_set_x(m_gui_object, (lv_coord_t)m_x); + } + } else { + m_isMoving = false; + } + } +} + +Rect Paddle::getBoundingBox() const { + return {m_x, m_y, m_width, m_height}; +} + +void Paddle::moveTo(float targetX) { + m_targetX = targetX; + m_isMoving = true; +} + +void Paddle::stopMovement() { + m_isMoving = false; +} + +float Paddle::getX() const { return m_x; } + +float Paddle::getY() const { return m_y; } \ No newline at end of file diff --git a/breakout/src/breakout.cpp b/breakout/src/breakout.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ed789a5bd2997596bc372d06755ec792899e3299 --- /dev/null +++ b/breakout/src/breakout.cpp @@ -0,0 +1,370 @@ +/****************************************************************** + * breakout.cpp + * Main game logic implementation file + * Contains all method implementations for the Game class, as well as + * the global game entry point ballgame_start(). + * The Game class manages the game state, object lifecycle, and core logic. + ********************************************************************/ + +#include "breakout.h" +#include "Paddle/Paddle.h" +#include "Ball/Ball.h" +#include "Brick/Brick.h" +#include "GameResourceManager/GameResourceManager.h" +#include <algorithm> +#include <cstdio> +#include <cmath> +#include <stdio.h> + +// Global pointer holding and managing the current unique game instance +Game* g_game_instance = nullptr; + +// ################################################# +// ## Implementation of Game class member functions +// ################################################# + +/** + * Constructor of the Game class + * @param parent The LVGL parent object on which the Game instance is created (usually the screen) + */ +Game::Game(lv_obj_t* parent) + : m_game_area(nullptr), + m_bricks(), + m_parent_screen(parent), + m_state(GameState::PLAYING), + m_game_timer(nullptr), + m_gameBounds(), + m_paddle(nullptr), + m_ball(nullptr), + m_resourceManager(nullptr) +{ + printf("[Game] Constructor called\n"); + init(); + printf("[Game] Init finished\n"); +} + +/** + * Destructor of the Game class + * Responsible for safely cleaning up all dynamically allocated resources upon object destruction to prevent memory leaks. + */ +Game::~Game() { + printf("[Game] Destructor called, starting resource cleanup.\n"); + + // 1. First, clean up the static resource cache + GameResourceManager::cleanupCache(); + if (m_resourceManager) { + m_resourceManager->stopAudio(); + delete m_resourceManager; + m_resourceManager = nullptr; + } + printf("[Game] Stopped audio\n"); + // 2. Delete the game timer + if (m_game_timer) { + lv_timer_del(m_game_timer); + m_game_timer = nullptr; + } + + // 3. Delete all brick objects + for (Brick* brick : m_bricks) { + delete brick; + } + m_bricks.clear(); + printf("[Game] Deleted bricks\n"); + // 4. Delete the paddle, ball, and resource manager instance + delete m_paddle; + delete m_ball; + + printf("[Game] Resource cleanup completed.\n"); +} +/** + * @brief Initialize all game elements, UI, and timers + */ +void Game::init() { + printf("[Game] Init Start\n"); + // Create the UI object serving as game background and container + m_game_area = lv_obj_create(m_parent_screen); + lv_obj_set_size(m_game_area, GAME_AREA_WIDTH, GAME_AREA_HEIGHT); + lv_obj_center(m_game_area); + lv_obj_set_style_bg_color(m_game_area, lv_color_white(), 0); + lv_obj_set_style_border_width(m_game_area, 0, 0); + lv_obj_set_style_radius(m_game_area, 0, 0); + lv_obj_set_style_pad_all(m_game_area, 0, 0); + lv_obj_clear_flag(m_game_area, LV_OBJ_FLAG_SCROLLABLE); + + // Define game logic boundaries (relative to m_game_area) + m_gameBounds = {0, 0, (float)GAME_AREA_WIDTH, (float)GAME_AREA_HEIGHT}; + + // Load level map + m_resourceManager = new GameResourceManager(); + m_resourceManager->loadBackground(m_game_area,"background_1.png"); + m_resourceManager->loadMap("level_1.dat", this); + + // Create game object instances + m_paddle = new Paddle(m_game_area, (GAME_AREA_WIDTH / 2.0f) - 50.0f, GAME_AREA_HEIGHT - 70.0f, 80.0f, 80.0f, m_gameBounds); + printf("[Game] Paddle created\n"); + m_ball = new Ball(m_game_area, 16.0f); + printf("[Game] Ball created\n"); + + // Create a transparent touch layer overlaying the game area to receive player input + lv_obj_t* touch_area = lv_obj_create(m_game_area); + lv_obj_set_size(touch_area, GAME_AREA_WIDTH, GAME_AREA_HEIGHT); + lv_obj_set_pos(touch_area, 0, 0); + lv_obj_set_style_bg_opa(touch_area, LV_OPA_TRANSP, 0); + lv_obj_set_style_border_width(touch_area, 0, 0); + lv_obj_add_event_cb(touch_area, touch_area_event_cb, LV_EVENT_ALL, this); + lv_obj_move_background(touch_area); // Move touch layer to the back to avoid covering other objects + + // Create the main game loop timer + m_game_timer = lv_timer_create(game_timer_cb, GAME_TICK_PERIOD, this); + printf("[Game] Init End\n"); +} + +/** + * Game main loop timer callback + * @param timer Pointer to the LVGL timer + */ +void Game::game_timer_cb(lv_timer_t* timer) { + // Retrieve the Game instance pointer from user data + Game* game = static_cast<Game*>(timer->user_data); + // State protection: do nothing if game is not in PLAYING state + if (game->m_state != GameState::PLAYING) return; + + const float deltaTime = (float)GAME_TICK_PERIOD / 1000.0f; + + // Update paddle position (reacting to player input) + game->m_paddle->update(deltaTime); + + // Handle ball states separately + if (game->m_ball->getState() == Ball::State::HELD) { + // HELD state: ball follows the paddle movement + game->m_ball->stickToPaddle(game->m_paddle->getBoundingBox()); + } else { + // MOVING state: update ball physics and perform collision detection + game->m_ball->update(deltaTime); + Vec2 ballPos = game->m_ball->getPosition(); + + // 1. Boundary collision detection + if (ballPos.x - game->m_ball->getRadius() < game->m_gameBounds.x || ballPos.x + game->m_ball->getRadius() > game->m_gameBounds.x + game->m_gameBounds.width) { + game->m_ball->bounceX(); + } + if (ballPos.y - game->m_ball->getRadius() < game->m_gameBounds.y) { + game->m_ball->bounceY(); + game->m_ball->boostSpeed(4.0f); + } + if (ballPos.y + game->m_ball->getRadius() > game->m_gameBounds.y + game->m_gameBounds.height) { + game->trigger_game_over(); + return; + } + + // 2. Collision detection with bricks + game->handleBallBrickCollision(); + + // 3. Collision detection with paddle + game->handleBallPaddleCollision(); + } +} +/** + * Handle collision between the ball and the bricks. + * Checks each active brick for collision with the ball. + * If a collision occurs, bounce the ball appropriately and apply damage or special logic. + */ +void Game::handleBallBrickCollision() { + Vec2 ballPos = m_ball->getPosition(); + + for (auto it = m_bricks.begin(); it != m_bricks.end(); ++it) { + Brick* brick = *it; + if (!brick->isActive()) continue; // Skip inactive bricks + if (!checkCollision(m_ball, brick)) continue; // Skip if no collision + + Rect brickRect = brick->getBoundingBox(); + Vec2 brickCenter = { + brickRect.x + brickRect.width / 2.0f, + brickRect.y + brickRect.height / 2.0f + }; + Vec2 offset = { + ballPos.x - brickCenter.x, + ballPos.y - brickCenter.y + }; + + // Calculate overlap distances on X and Y axes + float overlapX = m_ball->getRadius() + (brickRect.width / 2.0f) - fabsf(offset.x); + float overlapY = m_ball->getRadius() + (brickRect.height / 2.0f) - fabsf(offset.y); + + // Special case: Wall bricks (hp == -1) only bounce ball, do not get destroyed + if (brick->getHP() == -1) { + if (overlapX < overlapY) { + m_ball->bounceX(); // Bounce horizontally + // Correct ball position to avoid sticking inside the brick + ballPos.x = offset.x > 0 + ? brickRect.x + brickRect.width + m_ball->getRadius() + : brickRect.x - m_ball->getRadius(); + } else { + m_ball->bounceY(); // Bounce vertically + ballPos.y = offset.y > 0 + ? brickRect.y + brickRect.height + m_ball->getRadius() + : brickRect.y - m_ball->getRadius(); + } + m_ball->setPosition(ballPos); + m_ball->boostSpeed(4.0f); // Slightly increase ball speed after bounce + break; // Exit after first collision handled + } + m_resourceManager->playAudio(brick->getHP()); + // Normal brick logic: apply damage and bounce ball + brick->onHit(); // Reduce brick HP or mark as destroyed + + if (overlapX < overlapY) { + m_ball->bounceX(); + ballPos.x = offset.x > 0 + ? brickRect.x + brickRect.width + m_ball->getRadius() + : brickRect.x - m_ball->getRadius(); + } else { + m_ball->bounceY(); + ballPos.y = offset.y > 0 + ? brickRect.y + brickRect.height + m_ball->getRadius() + : brickRect.y - m_ball->getRadius(); + } + m_ball->setPosition(ballPos); + m_ball->boostSpeed(4.0f); + break; + } +} + + + + +/** + * Handle collision between the ball and the paddle. + * When collision is detected, the ball bounces off the paddle with an angle + * depending on the hit position on the paddle. + */ +void Game::handleBallPaddleCollision() { + if (!m_ball || !m_paddle) return; + + Vec2 ballPos = m_ball->getPosition(); + + if (checkCollision(m_ball, m_paddle)) { + Rect paddleRect = m_paddle->getBoundingBox(); + float paddleCenter = paddleRect.x + paddleRect.width / 2.0f; + + // Calculate hit factor relative to paddle center (-1 to 1) + float hitFactor = (ballPos.x - paddleCenter) / (paddleRect.width / 2.0f); + const float maxAngleFactor = 350.0f; // Max horizontal velocity factor + + Vec2 newVel = m_ball->getVelocity(); + newVel.x = hitFactor * maxAngleFactor; // Modify horizontal velocity based on hit position + + // Ensure the ball always bounces upward + if (newVel.y > 0) newVel.y = -newVel.y; + + m_ball->setVelocity(newVel); + + // Correct ball position to prevent it sticking inside the paddle + Vec2 correctedPos = ballPos; + correctedPos.y = paddleRect.y - m_ball->getRadius() - 1.0f; + m_ball->setPosition(correctedPos); + } +} +/** + * Touch event callback, handles player touch input (static member function) + */ +void Game::touch_area_event_cb(lv_event_t* e) { + Game* game = static_cast<Game*>(lv_event_get_user_data(e)); + if (game->m_state != GameState::PLAYING) return; + + lv_event_code_t code = lv_event_get_code(e); + if (code == LV_EVENT_PRESSED || code == LV_EVENT_PRESSING) { + // On press or drag, move the paddle + lv_point_t screen_point; + lv_indev_get_point(lv_event_get_indev(e), &screen_point); + lv_coord_t game_area_x = lv_obj_get_x(game->m_game_area); + lv_coord_t local_x = screen_point.x - game_area_x; + game->m_paddle->moveTo(local_x); + } else if (code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST) { + // On release, stop paddle movement and launch the ball + game->m_paddle->stopMovement(); + if (game->m_ball->getState() == Ball::State::HELD) { + game->m_ball->launch(); + } + } +} + +/** + * "Restart" button click event callback + */ +void Game::restart_button_event_cb(lv_event_t* e) { + + + // If an old game instance exists, delete it first to clean up resources + if (g_game_instance != nullptr) { + delete g_game_instance; + g_game_instance = nullptr; + } + + // Restarting the game just calls the global start function again + ballgame_start(); +} + +/** + * @brief Trigger the game over state and display UI + */ +void Game::trigger_game_over() { + m_state = GameState::GAME_OVER; + lv_obj_t* game_over_label = lv_label_create(m_parent_screen); + lv_label_set_text(game_over_label, "GAME OVER!"); + lv_obj_set_style_text_color(game_over_label, lv_color_black(), 0); + lv_obj_center(game_over_label); + + lv_obj_t* restart_btn = lv_button_create(m_parent_screen); + lv_obj_align_to(restart_btn, game_over_label, LV_ALIGN_OUT_BOTTOM_MID, -23, 20); + lv_obj_t* btn_label = lv_label_create(restart_btn); + lv_label_set_text(btn_label, "Restart"); + lv_obj_center(btn_label); + + lv_obj_add_event_cb(restart_btn, restart_button_event_cb, LV_EVENT_CLICKED, this); +} + +/** + * Collision detection helper - Ball and Paddle + */ +bool Game::checkCollision(const Ball* ball, const Paddle* paddle) { + Vec2 ballPos = ball->getPosition(); + float ballRadius = ball->getRadius(); + Rect paddleRect = paddle->getBoundingBox(); + float closestX = std::max(paddleRect.x, std::min(ballPos.x, paddleRect.x + paddleRect.width)); + float closestY = std::max(paddleRect.y, std::min(ballPos.y, paddleRect.y + paddleRect.height)); + float distanceX = ballPos.x - closestX; + float distanceY = ballPos.y - closestY; + return (distanceX * distanceX) + (distanceY * distanceY) < (ballRadius * ballRadius); +} + +/** + * Collision detection helper - Ball and Brick + */ +bool Game::checkCollision(const Ball* ball, const Brick* brick) { + Vec2 ballPos = ball->getPosition(); + float ballRadius = ball->getRadius(); + Rect brickRect = brick->getBoundingBox(); + float closestX = std::max(brickRect.x, std::min(ballPos.x, brickRect.x + brickRect.width)); + float closestY = std::max(brickRect.y, std::min(ballPos.y, brickRect.y + brickRect.height)); + float distanceX = ballPos.x - closestX; + float distanceY = ballPos.y - closestY; + return (distanceX * distanceX) + (distanceY * distanceY) < (ballRadius * ballRadius); +} + +// ################################################# +// ## Global startup function +// ################################################# + +/** + * Global game start/restart function + * Manages creation and destruction of the Game instance. + */ +void ballgame_start() { + lv_obj_t* screen = lv_screen_active(); + // Clean all old LVGL objects from the screen + lv_obj_clean(screen); + lv_obj_set_style_bg_color(screen, lv_color_black(), 0); + // Create a new game instance + g_game_instance = new Game(screen); +} + diff --git a/breakout/src/breakout.h b/breakout/src/breakout.h new file mode 100644 index 0000000000000000000000000000000000000000..91054d4d9a6fd2482fd9ca15017afb8f21f043c6 --- /dev/null +++ b/breakout/src/breakout.h @@ -0,0 +1,71 @@ +#ifndef BREAKOUT_H +#define BREAKOUT_H + +#pragma once + +#include "breakout_types.h" +#include <vector> // Include C++ vector container to manage list of bricks +#include <string> // Include C++ string class to handle file paths + +class Paddle; +class Ball; +class Brick; +class GameResourceManager; + +// Define core game states: playing or game over +enum class GameState { PLAYING, GAME_OVER }; + +// --- Main Game class definition --- +/** + * Main Game class (Game) + * Encapsulates all game states, objects, and core logic. + * Creates and initializes a game session in the constructor, + * and automatically cleans up all resources in the destructor. + */ +class Game { +public: + // --- Constructor and Destructor --- + Game(lv_obj_t* parent); // Constructor to create a game instance + ~Game(); // Destructor to destroy game instance and release all resources + + // Exposed for external modules (e.g., GameResourceManager) to access and modify directly during map loading + lv_obj_t* m_game_area; // Pointer to the UI container for the game area + std::vector<Brick*> m_bricks; // Dynamic array holding all brick objects + +private: + // State and core UI pointers + lv_obj_t* m_parent_screen; // Pointer to the parent screen where the game resides + GameState m_state; // Current game state (PLAYING or GAME_OVER) + lv_timer_t* m_game_timer; // Game main loop timer + + // Game area and boundaries + Rect m_gameBounds; // Logical bounds of the game area + + // Game logic objects + Paddle* m_paddle; // Paddle object + Ball* m_ball; // Ball object + GameResourceManager* m_resourceManager; // Resource manager object + +private: + // --- Internal helper functions --- + void init(); // Initialize the game + void trigger_game_over(); // Handle game over state + bool checkCollision(const Ball* ball, const Brick* brick); // Collision detection (ball-brick) + bool checkCollision(const Ball* ball, const Paddle* paddle); // Collision detection (ball-paddle) + void handleBallBrickCollision(); + void handleBallPaddleCollision(); + + // --- Static callback functions --- + static void game_timer_cb(lv_timer_t* timer); // Main loop timer callback + static void touch_area_event_cb(lv_event_t* e); // Touch input event callback + static void restart_button_event_cb(lv_event_t* e); // Restart button click callback +}; + +// --- Global start function prototype --- +/** + * Global game start/restart function interface + * The only external entry point to start or restart the entire game. + */ +void ballgame_start(); + +#endif // BREAKOUT_H \ No newline at end of file diff --git a/breakout/src/breakout_types.h b/breakout/src/breakout_types.h new file mode 100644 index 0000000000000000000000000000000000000000..05edca789cb7c5018fca658cc0e3d1ef458f063c --- /dev/null +++ b/breakout/src/breakout_types.h @@ -0,0 +1,14 @@ +#pragma once + +#include "lvgl.h" + +// 2D vector struct, represents position and velocity +struct Vec2 { float x = 0.0f; float y = 0.0f; }; + +// Rectangle struct, represents position, size, and bounds +struct Rect { float x = 0.0f; float y = 0.0f; float width = 0.0f; float height = 0.0f; }; + +// Define global constants for game screen size and timer period +static const int GAME_AREA_WIDTH = 1200; +static const int GAME_AREA_HEIGHT = 760; +static const int GAME_TICK_PERIOD = 20; // Game logic update period (milliseconds) \ No newline at end of file diff --git a/calculator/Kconfig b/calculator/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..41e5a1b8e9ddbde48bb5ed4ff45f1ef9bf7bfaa1 --- /dev/null +++ b/calculator/Kconfig @@ -0,0 +1,14 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config LVX_USE_DEMO_CALCULATOR + bool "CALCULATOR" + default n + +if LVX_USE_DEMO_CALCULATOR + config LVX_CALCULATOR_DATA_ROOT + string "Calculator Data Root" + default "/data" +endif diff --git a/calculator/Make.defs b/calculator/Make.defs new file mode 100644 index 0000000000000000000000000000000000000000..9397a98284b77451c0b96dc87425ab62d08df467 --- /dev/null +++ b/calculator/Make.defs @@ -0,0 +1,18 @@ +# +# Copyright (C) 2023 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +ifneq ($(CONFIG_LVX_USE_DEMO_CALCULATOR),) +CONFIGURED_APPS += $(APPDIR)/packages/demos/calculator +endif diff --git a/calculator/Makefile b/calculator/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..7f08a2a89476f82b352a93175a45f0b7677b5c4f --- /dev/null +++ b/calculator/Makefile @@ -0,0 +1,29 @@ +# +# Copyright (C) 2022 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +include $(APPDIR)/Make.defs + +ifeq ($(CONFIG_LVX_USE_DEMO_CALCULATOR), y) +PROGNAME = calculator +PRIORITY = 100 +STACKSIZE = 32768 +MODULE = $(CONFIG_LVX_USE_DEMO_CALCULATOR) +CXXEXT := .cpp +CXXSRCS = calculator_cre.cpp expression_calc.cpp +MAINSRC = calculator_main.cpp +endif + +include $(APPDIR)/Application.mk diff --git a/calculator/Readme.md b/calculator/Readme.md new file mode 100644 index 0000000000000000000000000000000000000000..c854388c5c6eec5e9280fdc92554422147cba3ef --- /dev/null +++ b/calculator/Readme.md @@ -0,0 +1,210 @@ +# 高级计算器 - 基于 open-vela + +## 📚 目录 + +- [一、基本介绍](#一基本介绍) + - [1.1 基本四则运算](#1-基本四则运算) + - [1.2 高级计算支持](#2-高级计算支持) + - [1.3 结果后输入智能处理](#3-结果后输入智能处理) + +- [二、实现思路](#二实现思路) + - [2.1 Token 级别与运算符优先级](#1-token-级别与运算符优先级) + - [2.2 表达式解析](#2-表达式解析) + - [2.3 逆波兰表示法计算](#3-逆波兰表示法计算) + - [2.4 基于 LVGL 使用 create_button() 动态创建按键](#4-基于-lvgl-使用-create_button-动态创建按键) + - [2.5 输入保护机制](#5-输入保护机制) + +- [三、使用说明](#三使用说明) + - [3.1 配置模拟器(menuconfig)](#1配置模拟器menuconfig) + - [3.2 makefile 编译问题](#2makefile编译问题) + - [3.3 资源推送更新 /res](#3资源推送更新-res) + +- [📁 原工程路径](#原工程路径) +- [🛠️ 常用指令](#常用指令) + +--- +## 一、基本介绍 + +- 基于 open-vela,制作了一个 **高级计算器**, +- 计算器函数部分引用自仓库https://github.com/W-Mai/ExpressionCalc +- 其中 calculate是计算结果,Clear是清屏,Del是退位。 +- sqrt以及cos等函数需要加括号使用,常量PI和E可以直接使用。 +例如 PI / 2 、 E + 4 、sqrt(4) 、cos(-2) + +![计算器界面](./screen_history/screenshot_commit7.png) + +该计算器实现了以下功能: + +### 1. 基本四则运算 + +- 支持加、减、乘、除运算 +- 运算支持 double 精度,仅显示有效数字,不显示多余的 `0` +- 例如:显示 `1.5`,而不是 `1.50000000` + +### 2. 高级计算支持 +使用方法。例如 cos(2) + +- sqrt 平方根(√) +- log 自然对数(ln) +- sin 正弦函数 +- cos 余弦函数 +- PI 圆周率 π 3.1415926... +- E 自然常数 e 2.7182818... +- . 小数点 +- ( 左括号 +- ) 右括号 + +### 3. 结果后输入智能处理 + +- 运算结果显示后,再次输入新数字会自动清屏 +- 如果输入的是运算符,则保留结果并继续运算 +--- + +## 二、实现思路 + +### 1. Token 级别与运算符优先级 + +- 通过 TokenLevel 来定义每个操作符的优先级。例如,+ 和 - 的优先级是 1,而 *, /, % 等是 2。 + +- 这样就能在处理逆波兰表示法时判断操作符的优先级,确保正确的运算顺序。 + + +### 2. 表达式解析 +- reversePolishNotation 方法负责将输入的中缀表达式(例如 a + b * c)转换为逆波兰表示法(如 a b c * +)。这个过程使用了栈来处理操作符和括号。 + +- 当遇到数字时,直接将其添加到结果队列。 + +- 当遇到操作符时,根据其优先级判断是否需要弹出栈中的操作符。 + +- 遇到左括号 ( 时,压入栈中,右括号 ) 时,弹出栈中的操作符直到遇到左括号。 + + +### 3. 逆波兰表示法计算 + +- evalNotation 方法通过栈来计算逆波兰表示法的结果。 + +- 如果遇到数字,将其压入栈中。 + +- 如果遇到运算符,从栈中弹出参数并进行计算,然后将结果压回栈中。 + +- 支持的运算符可以是自定义的,也可以是内置的,如 +, -, *, sin, log 等。 + + +### 4. 基于 LVGL 使用 `create_button()` 动态创建按键 + +- 通过 `create_button()` 函数动态创建按键 + +- 使用二维 `btn_map` 配置按钮排布,便于维护和扩展 + +- 使用 LVGL 的组件函数设置按钮及输入框,设定合理设置尺寸和位置,颜色 + +### 5. 输入保护机制 + +- 输入非法或不符合规则(如多个 `.`、负数开根号、除以零等)时,运算显示 `ERROR` + +- 删除、清除、错误重置等操作处理完善,确保系统稳定运行 + +--- + + +## 三、使用说明 +首先进入模拟器配置 +### 1.配置模拟器(menuconfig) +```bash +./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap menuconfig +``` +#### (1)编译设置 +- 配置LVX_USE_DEMO_CALCULATOR 为 yes +- LVX_CALCULATOR_DATA_ROOT的路径设置为/data (Kconfig文件默认预设为 /data) + +#### (2)使用C++头文件需要配置 +进行以下操作设置,以支持C++编写include头文件,例如iostream.h,cmath.h +- 设置 C++ library为 Toolchain C++ support +- 设置 C++ low level library select 为 GNU low level libsupc++ +- 设置 (gnu++20) Language standard + +如果使用异常处理机制try-catch,需要开启以下设置(嵌入式中不推荐,当前版本已不使用该机制) +- enable exception support + + +### 2.makefile编译问题 +#### (1)编译找不到Main函数 + +``` +arm-none-eabi-ld: /home/foam/vela-opensource/nuttx/staging/libapps.a(builtin_list.c.home.foam.vela-opensource.apps.builtin_1.o):(.rodata.g_builtins+0x7c): undefined reference to calculator_main' +``` +- 因为makefile文件中的PROGNAME = calculator +- 在编译时会试图链接一个函数名叫:int calculator_main(int argc, char *argv[]) +- 并且还需要加上extern "C" ,所以得到主函数为extern "C" int calculator_main 才能成功编译。 + +#### (2)makefile编写 +- 因为使用了cpp文件,所以需要增加CXXEXT以指出.cpp格式文件 +``` +CXXEXT := .cpp +``` +- 并且C++文件需要使用CXXSRCS标出,如 +``` +CXXSRCS = calculator_cre.cpp expression_calc.cpp +``` + +### 3.资源推送更新 /res +需要在模拟器运行状况下,使用ADB指令更新资源推送,才能显示更换的新图片和字体。 +- 启动模拟器 +```bash +./emulator.sh vela +``` +- ADB推送更新资源(在模拟器运行状况下,终端使用) +```bash +adb push apps/packages/demos/calculator/res /data/ +``` +- 之后启动计算器应用即可 +```bash +calculator & +``` + + +## 📁原工程路径 +vela-opensource/apps/packages/demos/calculator/ + +--- + +## 🛠️常用指令 + +### 开始构建 + +```bash +./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap -j$(nproc) +``` +### 配置模拟器(menuconfig) +```bash +./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap menuconfig +``` + +### ADB推送更新资源(在模拟器运行状况下) +```bash +adb push apps/packages/demos/calculator/res /data/ +``` + +### 清理构建产物 +```bash +./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap distclean -j$(nproc) +``` +### 启动模拟器 +```bash +./emulator.sh vela +``` + +### 启动计算器应用 +```bash +calculator & +``` +--- + + + + + + + + + diff --git a/calculator/calculator_cre.cpp b/calculator/calculator_cre.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ada4b4aadb608ed3133b95faf93d4176229feb44 --- /dev/null +++ b/calculator/calculator_cre.cpp @@ -0,0 +1,370 @@ +/* UI +*│ +*├── CalculatorState +*│ ├── Label: Text display for input and result +*│ ├── clear_on_next_input: bool flag to clear on next input +*│ ├── clear_error: bool flag to indicate error state +*│ +*├── Resource +*│ ├── Fonts: Loaded fonts of different sizes for various UI elements +*│ ├── Images: Paths to icon images like "background" +*│ ├── get_res_path(): Returns the resource directory path +*│ ├── get_fonts_path(): Returns the fonts directory path +*│ ├── get_icons_path(): Returns the icons directory path +*│ ├── load_resources(): Initializes fonts and images; returns false if any resource fails to load +*│ +*├── Functions +*│ ├── cleanup_resources(): ensuring no memory leaks occur during UI rendering cleanup. +*│ ├── app_create_main_page(): Creates the main UI page with background image +*│ ├── btn_input_cb(): Button click handler for input; appends the button text to the display +*│ ├── btn_clear_cb(): Button click handler for clearing the display +*│ ├── btn_del_cb(): Button click handler for deleting the last character +*│ ├── calc_btn_cb(): Button click handler for performing calculations +*│ ├── create_button(): Helper function to create buttons dynamically based on a map of button labels +*│ ├── calculator_create(): Main function to create the entire calculator UI, including buttons, display, and events +*│ +*├── Button Layout +*│ ├── Input Buttons: A grid of buttons representing calculator digits and functions like "sqrt", "log", "sin", "cos", etc. +*│ ├── Control Buttons: Additional buttons like "Clear", "Delete", and "Calculate" +*│ +*├── Button Event Handlers +*│ ├── btn_input_cb: Handles number and operator input, updating the display text +*│ ├── btn_clear_cb: Clears the display when the "Clear" button is pressed +*│ ├── btn_del_cb: Deletes the last character from the display when the "Delete" button is pressed +*│ ├── calc_btn_cb: Handles calculation logic when the "Calculate" button is pressed, evaluates the expression and displays the result +*│ +*└── UI Style and Appearance +* ├── Button Styles: Rounded corners, colors for different button states (default, pressed) +* ├── Label Styles: Font, border, background color, and opacity settings for input/output display +* ├── Background Image: A background image set in the main page +*/ + +#include "calculator_cre.h" + + +#include "lvgl.h" + + +void cleanup_resources(lv_draw_buf_t *draw_buf) { + if (draw_buf != NULL) { + lv_draw_buf_destroy(draw_buf); + } + LV_LOG_INFO("Resources cleaned up."); +} + +lv_draw_buf_t* app_create_main_page(lv_obj_t *parent, Resource* R) { + // Step 1: Create image decoder descriptor + lv_image_decoder_dsc_t decoder_dsc; + lv_image_decoder_args_t args = { 0 }; // Custom args if needed + lv_result_t res = lv_image_decoder_open(&decoder_dsc, R->images.background.c_str(), &args); + + if(res != LV_RESULT_OK) { + LV_LOG_ERROR("Image decode failed: %s", R->images.background.c_str()); + return NULL; + } + + // Step 2: Get decoded data (decoder_dsc.decoded) + const lv_draw_buf_t* const_buf = decoder_dsc.decoded; + + // Create a writable copy of the draw buffer + lv_draw_buf_t* draw_buf = lv_draw_buf_dup(const_buf); + + if(draw_buf == NULL) { + LV_LOG_ERROR("Failed to duplicate draw buffer."); + lv_image_decoder_close(&decoder_dsc); + return NULL; + } + + // Step 3: Use the decoded image (e.g., draw it to a canvas) + lv_obj_t* canvas = lv_canvas_create(parent); + lv_canvas_set_draw_buf(canvas, draw_buf); // Set the decoded buffer + + lv_obj_clear_flag(canvas, LV_OBJ_FLAG_CLICKABLE); + lv_obj_add_flag(canvas, LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS); + lv_obj_set_style_opa(canvas, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_move_background(canvas); + lv_obj_align(canvas, LV_ALIGN_CENTER, 0, 0); + lv_obj_set_size(canvas, 1280, 800); + + // Step 4: Clean up after we're done with the buffer + lv_image_decoder_close(&decoder_dsc); + return draw_buf; +} + + + +// Button input callback function +static void btn_input_cb(lv_event_t *e) { + auto btn = static_cast<lv_obj_t*>(lv_event_get_target(e)); + + // Retrieve the button text from the user data + auto txt = static_cast<const char*>(lv_obj_get_user_data(btn)); + auto state = static_cast<CalculatorState*>(lv_event_get_user_data(e)); // Get the calculator state + + auto old = lv_label_get_text(state->label); + std::string buf; + + // Check if the input is one of the special functions or constants (e.g., sin, cos, log, sqrt, PI, E) + bool is_function_or_constant = + strcmp(txt, "sin") == 0 || strcmp(txt, "cos") == 0 || strcmp(txt, "log") == 0 || + strcmp(txt, "sqrt") == 0 || strcmp(txt, "PI") == 0 || strcmp(txt, "E") == 0 || + strcmp(txt, "(") == 0 || strcmp(txt, ")") == 0; + + bool is_digit_or_func = std::isdigit(txt[0]) || is_function_or_constant; + bool should_clear = (state->clear_on_next_input && is_digit_or_func) || state->clear_error; + + if (should_clear) { + buf = txt; // Start with the current button text + state->clear_on_next_input = false; + state->clear_error = false; + } else { + buf = std::string(old) + txt; // Concatenate the old text with new input + state->clear_on_next_input = false; + } + + lv_label_set_text(state->label, buf.c_str()); // Update the label with the new text +} + + +// Callback function for the clear button +static void btn_clear_cb(lv_event_t *e) { + auto state = static_cast<CalculatorState*>(lv_event_get_user_data(e)); + lv_label_set_text(state->label, ""); // Clear the label text + state->clear_on_next_input = false; + state->clear_error = false; +} + +// Callback function for the delete button +static void btn_del_cb(lv_event_t *e) { + auto state = static_cast<CalculatorState*>(lv_event_get_user_data(e)); + auto txt = lv_label_get_text(state->label); + std::string str(txt); // Convert to std::string for easier manipulation + size_t len = str.length(); + + // If input should be cleared or there's an error, reset the label + if (state->clear_on_next_input || state->clear_error) { + lv_label_set_text(state->label, ""); + state->clear_on_next_input = false; + state->clear_error = false; + return; + } + + // Define keywords to delete as a whole + const std::vector<std::string> keywords = { "sqrt", "sin", "cos", "log", "PI" }; + + // Check if the current text ends with any of the keywords + for (const auto& kw : keywords) { + if (len >= kw.length() && str.compare(len - kw.length(), kw.length(), kw) == 0) { + str.erase(len - kw.length()); // Erase the entire keyword + lv_label_set_text(state->label, str.c_str()); + return; + } + } + + // Otherwise, delete the last character + if (len > 0) { + str.pop_back(); + lv_label_set_text(state->label, str.c_str()); + } +} + +// Callback function for the calculate button +static void calc_btn_cb(lv_event_t *e) { + auto state = static_cast<CalculatorState*>(lv_event_get_user_data(e)); // Get the user data passed into the event callback + auto expr = lv_label_get_text(state->label); + char result_buf[256]; + + // Process the expression to handle negative numbers correctly + std::string modified_expr = expr; + + // If the first character is '-', prepend '0' for negative number + if (modified_expr[0] == '-') { + modified_expr = "0" + modified_expr; // Change '-2' to '0-2' + } + + // For other negative numbers in the expression, check if '(-' is found + size_t pos = 0; + while ((pos = modified_expr.find("(-", pos)) != std::string::npos) { + modified_expr.replace(pos, 2, "(0-"); // Change '(-' to '(0-' + pos += 3; // Skip over the replaced text "(0-" to avoid infinite loop + } + + // Detect "PI" and append "()" if not followed by '(' + pos = 0; + while ((pos = modified_expr.find("PI", pos)) != std::string::npos) { + if (pos + 2 >= modified_expr.size() || modified_expr[pos + 2] != '(') { + modified_expr.insert(pos + 2, "()"); + pos += 4; // Move past "PI()" + } else { + pos += 3; // Skip over "PI(" + } + } + + // Detect "E" and append "()" if not followed by '(' + pos = 0; + while ((pos = modified_expr.find("E", pos)) != std::string::npos) { + if (pos + 1 >= modified_expr.size() || modified_expr[pos + 1] != '(') { + modified_expr.insert(pos + 1, "()"); + pos += 3; // Move past the inserted "E()" + } else { + pos += 2; // Already has '(', skip over "E(" + } + } + + + XCLZ::eXpressionCalc calc; + calc.setExpression(modified_expr); // Use the modified expression + + const auto rpn = calc.reversePolishNotation(); + if (calc.getError().type != XCLZ::ErrorType::Well) { + std::snprintf(result_buf, sizeof(result_buf), "Error: %s: %s", calc.errorToString().c_str(), calc.getError().msg.c_str()); + state->clear_on_next_input = true; + state->clear_error = true; + } else { + const auto val = calc.evalNotation(rpn); + if (calc.getError().type != XCLZ::ErrorType::Well) { + std::snprintf(result_buf, sizeof(result_buf), "Error: %s: %s", calc.errorToString().c_str(), calc.getError().msg.c_str()); + state->clear_on_next_input = true; + state->clear_error = true; + } else { + std::snprintf(result_buf, sizeof(result_buf), "%.8g", val); // Format the result + state->clear_on_next_input = true; + } + } + + lv_label_set_text(state->label, result_buf); // Update label with the result or error message +} + + +// Helper function to create a button +static void create_button(lv_obj_t *parent, const char *txt, int col, int row, CalculatorState* state, Resource* R) { + auto btn = lv_btn_create(parent); + lv_obj_set_size(btn, 100, 80); // Set button size + lv_obj_align(btn, LV_ALIGN_CENTER, col * 110 - 220, row * 90 - 80); // Position the button on the screen + + // Set style properties for the button + lv_obj_set_style_radius(btn, 30, LV_STATE_DEFAULT); // Rounded corners + lv_obj_set_style_border_width(btn, 2, LV_STATE_DEFAULT); // Border width + lv_obj_set_style_border_color(btn, lv_color_hex(0x000000), LV_STATE_DEFAULT); // Border color (black) + + // Set colors based on the row and column + if (col == 0) { // First column + lv_obj_set_style_bg_color(btn, lv_color_hex(0x2196F3), LV_STATE_DEFAULT); + lv_obj_set_style_bg_color(btn, lv_color_hex(0x5E3370), LV_STATE_PRESSED); + } else if (row == 0 && col != 0) { // First row except for the first button + lv_obj_set_style_bg_color(btn, lv_color_hex(0xFFE4C4), LV_STATE_DEFAULT); + lv_obj_set_style_bg_color(btn, lv_color_hex(0xEE82EE), LV_STATE_PRESSED); + } else { // Other buttons + lv_obj_set_style_bg_color(btn, lv_color_hex(0xFFFAFA), LV_STATE_DEFAULT); // Default color (light gray) + lv_obj_set_style_bg_color(btn, lv_color_hex(0xF5F5F5), LV_STATE_PRESSED); // Pressed color (light gray) + } + + // Create label for the button and set its text + auto btn_label = lv_label_create(btn); + lv_label_set_text(btn_label, txt); // Set the button label text + lv_obj_set_style_text_font(btn_label, R->fonts.size_22_bold, LV_STATE_DEFAULT); // Set the font + if (col == 0) { + lv_obj_set_style_text_color(btn_label, lv_color_hex(0xFFFFFF), LV_STATE_DEFAULT); // Set label color + } else if (row == 0 && col != 0) { // First row except for the first button + lv_obj_set_style_text_color(btn_label, lv_color_hex(0x4F4F4F), LV_STATE_DEFAULT); // Set label color + } else { // Other buttons + lv_obj_set_style_text_color(btn_label, lv_color_hex(0x000000), LV_STATE_DEFAULT); // Set label color + } + + lv_obj_center(btn_label); // Center the text inside the button + + // Store the button text in the user data for future reference + lv_obj_set_user_data(btn, (void*)txt); // Associate the button's text with the button object + + // Add event callback for the button click + lv_obj_add_event_cb(btn, btn_input_cb, LV_EVENT_CLICKED, state); +} + +// Function to create the entire calculator UI +lv_draw_buf_t* calculator_create(lv_obj_t *parent, CalculatorState* state, Resource* R) { + // Initialize resources (fonts and images) by calling load_resources + if (!R->load_resources()) { + // Handle error if resources failed to load + printf("Failed to load resources\n"); + }// Initialize resources (fonts and images) + auto draw_buf = app_create_main_page(parent, R); // Create the main page with the background + + // Input display box + auto screen = lv_scr_act(); // Get the current screen + lv_obj_set_style_bg_color(screen, lv_color_hex(0xF0F0F0), LV_STATE_DEFAULT); // Set gray background + state->label = lv_label_create(parent); + lv_obj_set_size(state->label, 540, 130); // Set label size + lv_obj_align(state->label, LV_ALIGN_TOP_MID, 0, 40); // Align label at the top center + lv_obj_set_style_text_font(state->label, R->fonts.size_48_normal, LV_STATE_DEFAULT); // Set label font + lv_obj_set_style_radius(state->label, 10, 0); // Set label border radius + lv_obj_set_style_border_width(state->label, 2, 0); // Set border width + lv_obj_set_style_border_color(state->label, lv_color_hex(0x000000), 0); // Set border color (black) + lv_obj_set_style_bg_color(state->label, lv_color_hex(0xE1FFFF), LV_STATE_DEFAULT); // Set background color (light cyan) + + // Ensure the background is fully opaque + lv_obj_set_style_bg_opa(state->label, LV_OPA_COVER, LV_STATE_DEFAULT); + lv_label_set_text(state->label, ""); // Clear label text initially + + // Input button matrix + const char *btn_map[5][5] = { + {"PI", "log", "sin", "cos", "sqrt"}, + {"E", "7", "8", "9", "+"}, + {"%", "4", "5", "6", "-"}, + {"^", "1", "2", "3", "*"}, + {".", "(", "0", ")", "/"} + }; + + // Create buttons from the button map + for (int row = 0; row < 5; ++row) { + for (int col = 0; col < 5; ++col) { + create_button(parent, btn_map[row][col], col, row, state, R); // Create each button + } + } + + // Calculate button + auto calc_btn = lv_btn_create(parent); + lv_obj_set_style_bg_color(calc_btn, lv_color_hex(0xFF8C00), LV_STATE_DEFAULT); // Purple background + lv_obj_set_style_radius(calc_btn, 35, LV_STATE_DEFAULT); // Rounded corners + lv_obj_set_style_border_width(calc_btn, 2, LV_STATE_DEFAULT); // Border width + lv_obj_set_style_border_color(calc_btn, lv_color_hex(0x000000), LV_STATE_DEFAULT); // Border color (black) + lv_obj_set_size(calc_btn, 130, 80); // Set button size + lv_obj_align(calc_btn, LV_ALIGN_CENTER, -200, -170); // Position the button on the screen + auto calc_lbl = lv_label_create(calc_btn); // Create button label + lv_label_set_text(calc_lbl, "calculate"); + lv_obj_set_style_text_font(calc_lbl, R->fonts.size_22_bold, LV_STATE_DEFAULT); + lv_obj_set_style_text_color(calc_lbl, lv_color_hex(0xFFFAFA), LV_STATE_DEFAULT); // Set label color + lv_obj_center(calc_lbl); // Center the label + lv_obj_add_event_cb(calc_btn, calc_btn_cb, LV_EVENT_CLICKED, state); // Add event callback + + // Clear button (Red) + auto clear_btn = lv_btn_create(parent); + lv_obj_set_style_bg_color(clear_btn, lv_color_hex(0xFF8C00), LV_STATE_DEFAULT); // Red color + lv_obj_set_size(clear_btn, 130, 80); + lv_obj_set_style_radius(clear_btn, 35, LV_STATE_DEFAULT); // Rounded corners + lv_obj_set_style_border_width(clear_btn, 2, LV_STATE_DEFAULT); // Border width + lv_obj_set_style_border_color(clear_btn, lv_color_hex(0x000000), LV_STATE_DEFAULT); // Border color (black) + lv_obj_align(clear_btn, LV_ALIGN_CENTER, 200, -170); // Position the button on the screen + auto clear_lbl = lv_label_create(clear_btn); + lv_label_set_text(clear_lbl, "Clear"); + lv_obj_set_style_text_font(clear_lbl, R->fonts.size_22_bold , LV_STATE_DEFAULT); + lv_obj_set_style_text_color(clear_lbl, lv_color_hex(0xFFFAFA), LV_STATE_DEFAULT); // Set label color + lv_obj_center(clear_lbl); + lv_obj_add_event_cb(clear_btn, btn_clear_cb, LV_EVENT_CLICKED, state); + + // Delete button (Green) + auto del_btn = lv_btn_create(parent); + lv_obj_set_style_bg_color(del_btn, lv_color_hex(0xFF8C00), LV_STATE_DEFAULT); // Green color + lv_obj_set_size(del_btn, 130, 80); + lv_obj_set_style_radius(del_btn, 35, LV_STATE_DEFAULT); // Rounded corners + lv_obj_set_style_border_width(del_btn, 2, LV_STATE_DEFAULT); // Border width + lv_obj_set_style_border_color(del_btn, lv_color_hex(0x000000), LV_STATE_DEFAULT); // Border color (black) + lv_obj_align(del_btn, LV_ALIGN_CENTER, 0, -170); // Position the button on the screen + auto del_lbl = lv_label_create(del_btn); + lv_label_set_text(del_lbl, "Del"); // ⌫ + lv_obj_set_style_text_font(del_lbl, R->fonts.size_22_bold , LV_STATE_DEFAULT); + lv_obj_set_style_text_color(del_lbl, lv_color_hex(0xFFFAFA), LV_STATE_DEFAULT); // Set label color + lv_obj_center(del_lbl); + lv_obj_add_event_cb(del_btn, btn_del_cb, LV_EVENT_CLICKED, state); + + return draw_buf; +} diff --git a/calculator/calculator_cre.h b/calculator/calculator_cre.h new file mode 100644 index 0000000000000000000000000000000000000000..ed2f2ebda803383fa0a123a0355e3b16a995bacf --- /dev/null +++ b/calculator/calculator_cre.h @@ -0,0 +1,98 @@ +#ifndef CALCULATOR_H +#define CALCULATOR_H + +#include "lvgl.h" +#include "expression_calc.h" +#include <cstring> +#define RES_ROOT CONFIG_LVX_CALCULATOR_DATA_ROOT + +class CalculatorState { +public: + lv_obj_t *label; + bool clear_on_next_input; + bool clear_error; + + CalculatorState() + : label(nullptr), clear_on_next_input(false), clear_error(false) {} +}; + +// C++ class for Resource +class Resource { +public: + // Fonts sub-structure + class Fonts { + public: + lv_font_t* size_16_normal; + lv_font_t* size_22_bold; + lv_font_t* size_24_normal; + lv_font_t* size_28_normal; + lv_font_t* size_48_normal; + lv_font_t* size_60_bold; + + Fonts() + : size_16_normal(nullptr), size_22_bold(nullptr), + size_24_normal(nullptr), size_28_normal(nullptr), + size_48_normal(nullptr), size_60_bold(nullptr) {} + }; + + // Images sub-structure + class Images { + public: + std::string background; + + Images() : background("") {} + }; + + // Member variables + Fonts fonts; + Images images; + + // Constructor to initialize resources + Resource() {} + + // Method to load the resources (fonts and images) + bool load_resources() { + std::string icons_path = get_icons_path(); + std::string fonts_path = get_fonts_path(); + + // Font initialization using std::string for paths + fonts.size_16_normal = lv_freetype_font_create((fonts_path + "/MiSans-Normal.ttf").c_str(), LV_FREETYPE_FONT_RENDER_MODE_BITMAP, 16, LV_FREETYPE_FONT_STYLE_NORMAL); + fonts.size_22_bold = lv_freetype_font_create((fonts_path + "/MiSans-Semibold.ttf").c_str(), LV_FREETYPE_FONT_RENDER_MODE_BITMAP, 22, LV_FREETYPE_FONT_STYLE_NORMAL); + fonts.size_24_normal = lv_freetype_font_create((fonts_path + "/MiSans-Normal.ttf").c_str(), LV_FREETYPE_FONT_RENDER_MODE_BITMAP, 24, LV_FREETYPE_FONT_STYLE_NORMAL); + fonts.size_28_normal = lv_freetype_font_create((fonts_path + "/MiSans-Normal.ttf").c_str(), LV_FREETYPE_FONT_RENDER_MODE_BITMAP, 28, LV_FREETYPE_FONT_STYLE_NORMAL); + fonts.size_48_normal = lv_freetype_font_create((fonts_path + "/MiSans-Normal.ttf").c_str(), LV_FREETYPE_FONT_RENDER_MODE_BITMAP, 48, LV_FREETYPE_FONT_STYLE_NORMAL); + fonts.size_60_bold = lv_freetype_font_create((fonts_path + "/MiSans-Semibold.ttf").c_str(), LV_FREETYPE_FONT_RENDER_MODE_BITMAP, 60, LV_FREETYPE_FONT_STYLE_NORMAL); + + // Image initialization + images.background = icons_path + "/background.png"; + printf("icons Path: %s\n", images.background.c_str()); + + // Check if fonts were loaded successfully + if (!fonts.size_16_normal || !fonts.size_22_bold || !fonts.size_24_normal || !fonts.size_28_normal || !fonts.size_48_normal || !fonts.size_60_bold ) { + return false; + } + + return true; + } + +private: + // Function to get the resource path + std::string get_res_path() { + return std::string(RES_ROOT) + "/res"; + } + + // Function to get the fonts path + std::string get_fonts_path() { + return get_res_path() + "/fonts"; + } + + // Function to get the icons path + std::string get_icons_path() { + return get_res_path() + "/icons"; + } +}; + +// Function to create the calculator UI +lv_draw_buf_t* calculator_create(lv_obj_t *parent, CalculatorState* state, Resource* R); +void cleanup_resources(lv_draw_buf_t *draw_buf); +#endif // CALCULATOR_H diff --git a/calculator/calculator_main.cpp b/calculator/calculator_main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3b9dc694960dbb4c8da2f8137ed57e7f4a21cdf9 --- /dev/null +++ b/calculator/calculator_main.cpp @@ -0,0 +1,73 @@ +// Include NuttX headers +#include <nuttx/config.h> +#include <unistd.h> +#include <uv.h> +#include "calculator_cre.h" // Include the header for calculator creation +// Include LVGL headers +#include <lvgl/lvgl.h> + +static void lv_nuttx_uv_loop(uv_loop_t* loop, lv_nuttx_result_t* result) +{ + lv_nuttx_uv_t uv_info; + void* data; + + uv_loop_init(loop); + + lv_memset(&uv_info, 0, sizeof(uv_info)); + uv_info.loop = loop; + uv_info.disp = result->disp; + uv_info.indev = result->indev; +#ifdef CONFIG_UINPUT_TOUCH + uv_info.uindev = result->utouch_indev; +#endif + + data = lv_nuttx_uv_init(&uv_info); + uv_run(loop, UV_RUN_DEFAULT); + lv_nuttx_uv_deinit(&data); +} + +extern "C" int calculator_main(int argc, FAR char *argv[]) +{ + lv_nuttx_dsc_t info; + lv_nuttx_result_t result; + uv_loop_t ui_loop; + lv_memset(&ui_loop, 0, sizeof(uv_loop_t)); + + if (lv_is_initialized()) { + LV_LOG_ERROR("LVGL already initialized! aborting."); + return -1; + } + + lv_init(); + lv_nuttx_dsc_init(&info); + lv_nuttx_init(&info, &result); + + if (result.disp == NULL) { + LV_LOG_ERROR("lv_demos initialization failure!"); + return 1; + } + + // Dynamically allocate memory for the CalculatorState object + CalculatorState* state = new CalculatorState(); + Resource* R = new Resource(); + + // Create the screen and calculator UI + lv_obj_t* scr = lv_screen_active(); + lv_draw_buf_t* draw_buf = calculator_create(scr, state, R); // Create the calculator UI + + lv_nuttx_uv_loop(&ui_loop, &result); + + lv_nuttx_deinit(&result); + lv_deinit(); + + // Clean up: delete allocated objects + if (draw_buf) { + cleanup_resources(draw_buf); + } else { + LV_LOG_WARN("no resources to clean."); + } + delete state; + delete R; + + return 0; +} diff --git a/calculator/expression_calc.cpp b/calculator/expression_calc.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6cd1ddaf9309aad235b83eb788527eaaa5aa91bb --- /dev/null +++ b/calculator/expression_calc.cpp @@ -0,0 +1,279 @@ +/* + * expression_calc.cpp + * + * MIT License + * Copyright (c) 2022 XCLZ STUDIO@W-Mai + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "readability-convert-member-functions-to-static" + +#include "expression_calc.h" + +#include <utility> + +#define ERROR(type_, msg_, tag_) \ + Error.type = (type_); \ + Error.msg = (msg_); \ + goto tag_ + +namespace XCLZ { + +eXpressionCalc::eXpressionCalc() { + ErrorType2Name = { + {ErrorType::Well, "Well Done" }, + { ErrorType::BracketNotMatched, "Bracket Not Matched"}, + { ErrorType::FunctionNotFound, "Function Not Found" }, + { ErrorType::EvalError, "Evaluate Error" }, + { ErrorType::SyntaxError, "Syntax Error" } + }; + + TokenLevel = { + {'+', 1}, + { '-', 1}, + { '*', 2}, + { '/', 2}, + { '^', 2}, + { '%', 2}, + { '\\', 2}, + { '(', 0}, + { ')', 0} + }; + + NoteTable = { + {"+", { 2, LAMBDA_EXPR(params[0] + params[1]) } }, + { "-", { 2, LAMBDA_EXPR(params[0] - params[1]) } }, + { "*", { 2, LAMBDA_EXPR(params[0] * params[1]) } }, + { "/", { 2, LAMBDA_EXPR(params[0] / params[1]) } }, + { "^", { 2, LAMBDA_EXPR(pow(params[0], params[1])) } }, + { "%", { 2, LAMBDA_EXPR(fmod(params[0], params[1])) } }, + { "\\", { 2, LAMBDA_EXPR((int)((int)params[0] / (int)params[1])) } }, + { "addFunc", { 2, LAMBDA_EXPR(params[0] + params[1]) } }, + { "sub", { 2, LAMBDA_EXPR(params[0] - params[1]) } }, + { "mul", { 2, LAMBDA_EXPR(params[0] * params[1]) } }, + { "div", { 2, LAMBDA_EXPR(params[0] / params[1]) } }, + { "pow", { 2, LAMBDA_EXPR(pow(params[0], params[1])) } }, + { "mod", { 2, LAMBDA_EXPR(fmod(params[0], params[1])) } }, + { "divi", { 2, LAMBDA_EXPR((int)((int)params[0] / (int)params[1])) } }, + { "sqrt", { 1, LAMBDA_EXPR(sqrt(params[0])) } }, + { "abs", { 1, LAMBDA_EXPR(fabs(params[0])) } }, + { "sin", { 1, LAMBDA_EXPR(sin(params[0])) } }, + { "cos", { 1, LAMBDA_EXPR(cos(params[0])) } }, + { "tan", { 1, LAMBDA_EXPR(tan(params[0])) } }, + { "asin", { 1, LAMBDA_EXPR(asin(params[0])) } }, + { "acos", { 1, LAMBDA_EXPR(acos((params[0]))) } }, + { "atan", { 1, LAMBDA_EXPR(atan(params[0])) } }, + { "ln", { 1, LAMBDA_EXPR(log(params[0])) } }, + { "log", { 1, LAMBDA_EXPR(log10(params[0])) } }, + { "log2", { 1, LAMBDA_EXPR(log2(params[0])) } }, + { "floor", { 1, LAMBDA_EXPR(floor(params[0])) } }, + { "ceil", { 1, LAMBDA_EXPR(ceil(params[0])) } }, + { "sign", { 1, LAMBDA_EXPR(abs(params[0]) < 1e-10 ? 0 : params[0] > 0 ? 1 + : -1) }}, + { "PI", { 0, LAMBDA_EXPR(M_PI) } }, + { "E", { 0, LAMBDA_EXPR(M_E) } }, + }; +} + +void eXpressionCalc::resetError() { + Error.type = ErrorType::Well; + Error.msg = ""; +} + +inline bool eXpressionCalc::checkNumber() { + return isdigit(*ExprIt) + || ((ExprIt == Expr.begin() || *(ExprIt - 1) == '(') + && ((*ExprIt == '-' || *ExprIt == '+') && (ExprIt + 1) != Expr.end())) + || ((*ExprIt == '.') && (ExprIt + 1) != Expr.end() && isdigit(*(ExprIt + 1))); +} + +std::string eXpressionCalc::readNumber() { + int dot_count = 0; + std::string rtn; + while (ExprIt != Expr.end() && checkNumber()) { + if (*ExprIt == '.') dot_count++; + if (dot_count > 1) break; + if (*ExprIt != '+') rtn += *ExprIt++; + else ++ExprIt; + } + return rtn; +} + +inline bool eXpressionCalc::checkFunc() { + return isalnum(*ExprIt); +} + +std::string eXpressionCalc::readFunc() { + std::string rtn; + while (ExprIt != Expr.end() && checkFunc()) rtn += *ExprIt++; + return rtn; +} + +bool eXpressionCalc::eatWhitespace() { + while (ExprIt != Expr.end() && (isspace(*ExprIt) || *ExprIt == ',')) ExprIt++; + return ExprIt == Expr.end(); +} + +bool eXpressionCalc::isNumber(const Expression_t& expr) { + return std::all_of(expr.begin(), expr.end(), [](char ch) -> bool { + return isdigit(ch) || ch == '.'; + }); +} + +Notation_t eXpressionCalc::reversePolishNotation() { + resetError(); + Notation_t result; + std::vector<std::string::const_iterator> buffer; + std::stack<std::pair<std::string, std::string::const_iterator>> tmpFunc; + for (ExprIt = Expr.begin(); ExprIt != Expr.end();) { + if (eatWhitespace()) + break; + + auto ch = *ExprIt; + if (!checkNumber()) { + if (checkFunc()) { + tmpFunc.push({ readFunc(), ExprIt }); + continue; + } + if (ch == '(' || buffer.empty()) { + buffer.push_back(ExprIt); + } else if (ch == ')') { + while (!buffer.empty() && *buffer.back() != '(') { + result.push_back(std::string { *buffer.back() }); + buffer.pop_back(); + } + + if (!tmpFunc.empty() && buffer.back() == tmpFunc.top().second) { + result.push_back(tmpFunc.top().first); + tmpFunc.pop(); + } + + if (buffer.empty()) { + ERROR(ErrorType::BracketNotMatched, std::string({ '(' }), ERROR_BUT_RETURN_RESULT_TAG); + } else { + buffer.pop_back(); + } + + } else { + auto search_res_ch = TokenLevel.find(ch); + auto search_res_back = TokenLevel.find(*buffer.back()); + if (search_res_ch == TokenLevel.end()) { + ERROR(ErrorType::SyntaxError, std::string({ ch }), ERROR_BUT_RETURN_RESULT_TAG); + } + if (search_res_back == TokenLevel.end()) { + ERROR(ErrorType::SyntaxError, std::string({ *buffer.back() }), ERROR_BUT_RETURN_RESULT_TAG); + } + if (TokenLevel.at(ch) <= TokenLevel.at(*buffer.back())) { + while (!buffer.empty() && TokenLevel.at(ch) <= TokenLevel.at(*buffer.back())) { + result.push_back(std::string { *buffer.back() }); + buffer.pop_back(); + } + } + buffer.push_back(ExprIt); + } + ++ExprIt; + } else { + std::string num = readNumber(); + result.push_back(num); + } + } + + while (!buffer.empty()) { + result.push_back(std::string { *buffer.back() }); + buffer.pop_back(); + } + +ERROR_BUT_RETURN_RESULT_TAG: + return result; +} + +double eXpressionCalc::evalNotation(const Notation_t& notation) { + resetError(); + if (notation.empty()) + return INFINITY; + + std::stack<double> resultStack; + + for (auto& note : notation) { + if (isNumber(note)) { + resultStack.push(strtod(note.c_str(), nullptr)); + } else { + if (note == "(" || note == ")") { + ERROR(ErrorType::BracketNotMatched, note, ERROR_BUT_RETURN_INFINITY_TAG); + } + + auto nt_it = NoteTable.find(note); + if (nt_it == NoteTable.end()) { + ERROR(ErrorType::FunctionNotFound, note, ERROR_BUT_RETURN_INFINITY_TAG); + } + + const auto pCount = nt_it->second.first; + const auto param = new double[pCount]; + for (auto i = pCount - 1; i >= 0; i--) { + if (resultStack.empty()) { + delete[] param; + ERROR(ErrorType::SyntaxError, note, ERROR_BUT_RETURN_INFINITY_TAG); + } + param[i] = resultStack.top(); + resultStack.pop(); + } + + resultStack.push(nt_it->second.second(param, pCount)); + + delete[] param; + } + } + + if (resultStack.size() > 1) { + ERROR(ErrorType::EvalError, "Can't Evaluate Correctly", ERROR_BUT_RETURN_RESULT_TAG); + } + +ERROR_BUT_RETURN_RESULT_TAG: + return resultStack.top(); +ERROR_BUT_RETURN_INFINITY_TAG: + return INFINITY; +} + +void eXpressionCalc::setExpression(Expression_t expression) { + Expr = std::move(expression); +} + +const Error_t& eXpressionCalc::getError() { + return Error; +} + +std::string eXpressionCalc::errorToString() { + return ErrorType2Name[getError().type]; +} + +void eXpressionCalc::addFunc(const std::string& note, int pNum, MyFunc_t func) { + NoteTable[note] = { + pNum, + func + }; +} + +void eXpressionCalc::addToken(char token, int level) { + TokenLevel[token] = level; +} +} + +#pragma clang diagnostic pop diff --git a/calculator/expression_calc.h b/calculator/expression_calc.h new file mode 100644 index 0000000000000000000000000000000000000000..e6122d26f924e2fdd53abcd5aacdf4bdccb547e7 --- /dev/null +++ b/calculator/expression_calc.h @@ -0,0 +1,100 @@ +/* + * expression_calc.h + * Created by W-Mai on 2022/10/2. + * + * MIT License + * Copyright (c) 2022 XCLZ STUDIO@W-Mai + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef EXPRESSIONCALC_EXPRESSION_CALC_H +#define EXPRESSIONCALC_EXPRESSION_CALC_H + +#include <algorithm> +#include <cmath> +#include <iostream> +#include <map> +#include <stack> +#include <string> +#include <vector> + +#define LAMBDA_EXPR(expr) [](const double* params, const int num) -> double { return expr; } + +namespace XCLZ { + +enum class ErrorType { + Well, + FunctionNotFound, + EvalError, + SyntaxError, + BracketNotMatched +}; + +typedef struct Error { + ErrorType type = ErrorType::Well; + std::string msg; +} Error_t; + +typedef std::map<char, int> TokenLevel_t; +typedef std::vector<std::string> Notation_t; +typedef std::string Expression_t; +typedef std::string String_t; +typedef std::string::const_iterator ExpressionIt_t; +typedef std::map<ErrorType, std::string> ErrorType2Name_t; + +typedef double (*MyFunc_t)(const double* params, const int num); +typedef std::map<std::string, std::pair<int, MyFunc_t>> NoteTable_t; + +// Class Declaration +class eXpressionCalc { + +private: + ErrorType2Name_t ErrorType2Name; + TokenLevel_t TokenLevel; + NoteTable_t NoteTable; + Error_t Error; + + Expression_t Expr; + ExpressionIt_t ExprIt; + + void resetError(); + + bool eatWhitespace(); + bool checkNumber(); + String_t readNumber(); + bool checkFunc(); + String_t readFunc(); + bool isNumber(const Expression_t& expr); + +public: + eXpressionCalc(); + Notation_t reversePolishNotation(); + double evalNotation(const Notation_t& notation); + + void setExpression(Expression_t expression); + const Error_t& getError(); + String_t errorToString(); + + void addFunc(const String_t& note, int pNum, MyFunc_t func); + void addToken(char token, int level); +}; +} + +#endif // EXPRESSIONCALC_EXPRESSION_CALC_H diff --git a/calculator/res/fonts/MiSans-Normal.ttf b/calculator/res/fonts/MiSans-Normal.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3199d464141d0599dd1269a3fef4264a829c6da0 Binary files /dev/null and b/calculator/res/fonts/MiSans-Normal.ttf differ diff --git a/calculator/res/fonts/MiSans-Semibold.ttf b/calculator/res/fonts/MiSans-Semibold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..83c1a532632f582550d48bae117d758191ad1800 Binary files /dev/null and b/calculator/res/fonts/MiSans-Semibold.ttf differ diff --git a/calculator/res/icons/background.png b/calculator/res/icons/background.png new file mode 100644 index 0000000000000000000000000000000000000000..b3cb7909b7bdb9cde8d501099ac161d8bb801e5b Binary files /dev/null and b/calculator/res/icons/background.png differ diff --git a/calculator/screen_history/screenshot_commit1.jpg b/calculator/screen_history/screenshot_commit1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..da591844bcf9f1cadcff64f92336513814c1f3bb Binary files /dev/null and b/calculator/screen_history/screenshot_commit1.jpg differ diff --git a/calculator/screen_history/screenshot_commit4.png b/calculator/screen_history/screenshot_commit4.png new file mode 100644 index 0000000000000000000000000000000000000000..9984ac0bcfe90ebc329130fa1bf0c5654c630c88 Binary files /dev/null and b/calculator/screen_history/screenshot_commit4.png differ diff --git a/calculator/screen_history/screenshot_commit5.png b/calculator/screen_history/screenshot_commit5.png new file mode 100644 index 0000000000000000000000000000000000000000..dc4b3756ba583b9db322932a5b09c7292c688f17 Binary files /dev/null and b/calculator/screen_history/screenshot_commit5.png differ diff --git a/calculator/screen_history/screenshot_commit7.png b/calculator/screen_history/screenshot_commit7.png new file mode 100644 index 0000000000000000000000000000000000000000..6725b7605d817643a8fce68e9c41b17b7bdef4cd Binary files /dev/null and b/calculator/screen_history/screenshot_commit7.png differ diff --git a/music_player/Kconfig b/music_player/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..f6534204589fe84d915ecf01d7eb9c3077365cd5 --- /dev/null +++ b/music_player/Kconfig @@ -0,0 +1,14 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config LVX_USE_DEMO_MUSIC_PLAYER + bool "Music Player" + default n + +if LVX_USE_DEMO_MUSIC_PLAYER + config LVX_MUSIC_PLAYER_DATA_ROOT + string "Music Player Data Root" + default "/sdcard" +endif diff --git a/music_player/Make.defs b/music_player/Make.defs new file mode 100644 index 0000000000000000000000000000000000000000..67bf18180e861389b6620e3e62c024cbd725cab0 --- /dev/null +++ b/music_player/Make.defs @@ -0,0 +1,18 @@ +# +# Copyright (C) 2023 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +ifneq ($(CONFIG_LVX_USE_DEMO_MUSIC_PLAYER),) +CONFIGURED_APPS += $(APPDIR)/packages/demos/music_player +endif diff --git a/music_player/Makefile b/music_player/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..ed7255f5a947f2a8df28adfb4b1ae8c2996a7cdc --- /dev/null +++ b/music_player/Makefile @@ -0,0 +1,29 @@ +# +# Copyright (C) 2022 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +include $(APPDIR)/Make.defs + +ifeq ($(CONFIG_LVX_USE_DEMO_MUSIC_PLAYER), y) +PROGNAME = music_player +PRIORITY = 100 +STACKSIZE = 32768 +MODULE = $(CONFIG_LVX_USE_DEMO_MUSIC_PLAYER) + +CSRCS = music_player.c audio_ctl.c wifi.c +MAINSRC = music_player_main.c +endif + +include $(APPDIR)/Application.mk diff --git a/music_player/audio_ctl.c b/music_player/audio_ctl.c new file mode 100644 index 0000000000000000000000000000000000000000..765c6f437451bc80ac089d6757a758aa82a3edd6 --- /dev/null +++ b/music_player/audio_ctl.c @@ -0,0 +1,287 @@ +/********************* + * INCLUDES + *********************/ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include "audio_ctl.h" + +#include <audioutils/nxaudio.h> + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void app_dequeue_cb(unsigned long arg, + FAR struct ap_buffer_s *apb); +static void app_complete_cb(unsigned long arg); +static void app_user_cb(unsigned long arg, + FAR struct audio_msg_s *msg, FAR bool *running); + +/********************** + * STATIC VARIABLES + **********************/ + +static struct nxaudio_callbacks_s cbs = +{ + app_dequeue_cb, + app_complete_cb, + app_user_cb +}; + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void app_dequeue_cb(unsigned long arg, FAR struct ap_buffer_s *apb) +{ + FAR audioctl_s *ctl = (FAR audioctl_s *)(uintptr_t)arg; + + if (!apb) + { + return; + } + + if (ctl->seek) { + lseek(ctl->fd, ctl->seek_position, SEEK_SET); + ctl->file_position = ctl->seek_position; + ctl->seek = false; + } + + apb->nbytes = read(ctl->fd, apb->samp, apb->nmaxbytes); + apb->curbyte = 0; + apb->flags = 0; + + while (0 < apb->nbytes && apb->nbytes < apb->nmaxbytes) + { + int n = apb->nmaxbytes - apb->nbytes; + int ret = read(ctl->fd, &apb->samp[apb->nbytes], n); + + if (0 >= ret) + { + break; + } + apb->nbytes += ret; + } + + if (apb->nbytes < apb->nmaxbytes) + { + close(ctl->fd); + ctl->fd = -1; + + return ; + } + + ctl->file_position += apb->nbytes; + + nxaudio_enqbuffer(&ctl->nxaudio, apb); +} + +static void app_complete_cb(unsigned long arg) +{ + /* Do nothing.. */ + + printf("Audio loop is Done\n"); +} + +static void app_user_cb(unsigned long arg, + FAR struct audio_msg_s *msg, FAR bool *running) +{ + /* Do nothing.. */ +} + +static FAR void *audio_loop_thread(pthread_addr_t arg) +{ + FAR audioctl_s *ctl = (FAR audioctl_s *)arg; + + nxaudio_start(&ctl->nxaudio); + nxaudio_msgloop(&ctl->nxaudio, &cbs, + (unsigned long)(uintptr_t)ctl); + + return NULL; +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +FAR audioctl_s *audio_ctl_init_nxaudio(FAR const char *arg) +{ + FAR audioctl_s *ctl; + int ret; + int i; + + ctl = (FAR audioctl_s *)malloc(sizeof(audioctl_s)); + if(ctl == NULL) + { + return NULL; + } + + ctl->seek = false; + ctl->seek_position = 0; + ctl->file_position = 0; + + ctl->fd = open(arg, O_RDONLY); + if (ctl->fd < 0) { + printf("can't open audio file\n"); + return NULL; + } + + read(ctl->fd, &ctl->wav, sizeof(ctl->wav)); + + ret = init_nxaudio(&ctl->nxaudio, ctl->wav.fmt.samplerate, + ctl->wav.fmt.bitspersample, + ctl->wav.fmt.numchannels); + if (ret < 0) + { + printf("init_nxaudio() return with error!!\n"); + return NULL; + } + + for (i = 0; i < ctl->nxaudio.abufnum; i++) + { + app_dequeue_cb((unsigned long)ctl, ctl->nxaudio.abufs[i]); + } + + ctl->state = AUDIO_CTL_STATE_INIT; + + return ctl; +} + +int audio_ctl_start(FAR audioctl_s *ctl) +{ + if (ctl == NULL) + return -EINVAL; + + if (ctl->state != AUDIO_CTL_STATE_INIT && ctl->state != AUDIO_CTL_STATE_PAUSE) + { + return -1; + } + + ctl->state = AUDIO_CTL_STATE_START; + + pthread_attr_t tattr; + struct sched_param sparam; + + pthread_attr_init(&tattr); + sparam.sched_priority = sched_get_priority_max(SCHED_FIFO) - 9; + pthread_attr_setschedparam(&tattr, &sparam); + pthread_attr_setstacksize(&tattr, 4096); + + pthread_create(&ctl->pid, &tattr, audio_loop_thread, + (pthread_addr_t)ctl); + + pthread_attr_destroy(&tattr); + pthread_setname_np(ctl->pid, "audioctl_thread"); + + return 0; +} + +int audio_ctl_pause(FAR audioctl_s *ctl) +{ + if (ctl == NULL) + return -EINVAL; + + if (ctl->state != AUDIO_CTL_STATE_START) + { + return -1; + } + + ctl->state = AUDIO_CTL_STATE_PAUSE; + + return nxaudio_pause(&ctl->nxaudio); +} + +int audio_ctl_resume(FAR audioctl_s *ctl) +{ + if (ctl == NULL) + return -EINVAL; + + if (ctl->state != AUDIO_CTL_STATE_PAUSE) + { + return -1; + } + + ctl->state = AUDIO_CTL_STATE_START; + + return nxaudio_resume(&ctl->nxaudio); +} + +int audio_ctl_seek(FAR audioctl_s *ctl, unsigned ms) +{ + if (ctl == NULL) + return -EINVAL; + + ctl->seek_position = ms * ctl->wav.fmt.samplerate * ctl->wav.fmt.bitspersample * ctl->wav.fmt.numchannels / 8; + ctl->seek = true; + + return 0; +} + +int audio_ctl_stop(FAR audioctl_s *ctl) +{ + if (ctl == NULL) + return -EINVAL; + + if (ctl->state != AUDIO_CTL_STATE_PAUSE && ctl->state != AUDIO_CTL_STATE_START) + { + return -1; + } + + ctl->state = AUDIO_CTL_STATE_STOP; + + nxaudio_stop(&ctl->nxaudio); + + if (ctl->pid > 0) + { + pthread_join(ctl->pid, NULL); + } + + return 0; +} + +int audio_ctl_set_volume(FAR audioctl_s *ctl, uint16_t vol) +{ + if (ctl == NULL) + return -EINVAL; + + return nxaudio_setvolume(&ctl->nxaudio, vol); +} + +int audio_ctl_get_position(FAR audioctl_s *ctl) +{ + if (ctl == NULL) + return -EINVAL; + + return ctl->file_position / (ctl->wav.fmt.bitspersample * ctl->wav.fmt.numchannels * ctl->wav.fmt.samplerate / 8); +} + +int audio_ctl_uninit_nxaudio(FAR audioctl_s *ctl) +{ + if (ctl == NULL) + return -EINVAL; + + if (ctl->state == AUDIO_CTL_STATE_NOP) + { + return 0; + } + + if (ctl->fd > 0) + { + close(ctl->fd); + ctl->fd = -1; + } + + fin_nxaudio(&ctl->nxaudio); + + free(ctl); + + return 0; +} diff --git a/music_player/audio_ctl.h b/music_player/audio_ctl.h new file mode 100644 index 0000000000000000000000000000000000000000..e7e60993b3da128a7917731c5f59195a718653f2 --- /dev/null +++ b/music_player/audio_ctl.h @@ -0,0 +1,85 @@ +#ifndef AUDIO_CTL_H +#define AUDIO_CTL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include <audioutils/nxaudio.h> +#include <pthread.h> + +enum { + AUDIO_CTL_STATE_NOP, + AUDIO_CTL_STATE_INIT, + AUDIO_CTL_STATE_START, + AUDIO_CTL_STATE_PAUSE, + AUDIO_CTL_STATE_STOP, +}; + +typedef struct wav_riff { + /* chunk "riff" */ + char chunkID[4]; /* "RIFF" */ + /* sub-chunk-size */ + uint32_t chunksize; /* 36 + subchunk2size */ + /* sub-chunk-data */ + char format[4]; /* "WAVE" */ +} riff_s; + +typedef struct wav_fmt { + /* sub-chunk "fmt" */ + char subchunk1ID[4]; /* "fmt " */ + /* sub-chunk-size */ + uint32_t subchunk1size; /* 16 for PCM */ + /* sub-chunk-data */ + uint16_t audioformat; /* PCM = 1*/ + uint16_t numchannels; /* Mono = 1, Stereo = 2, etc. */ + uint32_t samplerate; /* 8000, 44100, etc. */ + uint32_t byterate; /* = samplerate * numchannels * bitspersample/8 */ + uint16_t blockalign; /* = numchannels * bitspersample/8 */ + uint16_t bitspersample; /* 8bits, 16bits, etc. */ +} fmt_s; + +typedef struct wav_data { + /* sub-chunk "data" */ + char subchunk2ID[4]; /* "data" */ + /* sub-chunk-size */ + uint32_t subchunk2size; /* data size */ + /* sub-chunk-data */ + //Data_block_t block; +} data_s; + +typedef struct wav_fotmat { + riff_s riff; + fmt_s fmt; + data_s data; +} wav_s; + +typedef struct audioctl { + struct nxaudio_s nxaudio; + wav_s wav; + int fd; + int state; + pthread_t pid; + int seek; + uint32_t seek_position; + uint32_t file_position; +} audioctl_s; + +FAR audioctl_s *audio_ctl_init_nxaudio(FAR const char *arg); +int audio_ctl_start(FAR audioctl_s *ctl); +int audio_ctl_pause(FAR audioctl_s *ctl); +int audio_ctl_resume(FAR audioctl_s *ctl); +int audio_ctl_seek(FAR audioctl_s *ctl, unsigned ms); +int audio_ctl_stop(FAR audioctl_s *ctl); +int audio_ctl_set_volume(FAR audioctl_s *ctl, uint16_t vol); +int audio_ctl_get_position(FAR audioctl_s *ctl); +int audio_ctl_uninit_nxaudio(FAR audioctl_s *ctl); + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /* AUDIO_CTL_H */ diff --git a/music_player/music_player.c b/music_player/music_player.c new file mode 100644 index 0000000000000000000000000000000000000000..d1ea2c2e19ee67a66c67b0f9eed9d5994cd26af1 --- /dev/null +++ b/music_player/music_player.c @@ -0,0 +1,1033 @@ +// +// Created by BenignX on 2024/3/21. +// + +/* + * UI: + * + * TIME GROUP: + * TIME: 00:00:00 + * DATE: 2024/03/21 + * + * PLAYER GROUP: + * ALBUM GROUP: + * ALBUM PICTURE + * ALBUM INFO: + * ALBUM NAME + * ALBUM ARTIST + * PROGRESS GROUP: + * CURRENT TIME: 00:00/00:00 + * PLAYBACK PROGRESS BAR + * CONTROL GROUP: + * PLAYLIST + * PREVIOUS + * PLAY/PAUSE + * NEXT + * AUDIO + * + * TOP Layer: + * VOLUME BAR + * PLAYLIST GROUP: + * TITLE + * LIST: + * ICON + * ALBUM NAME + * ALBUM ARTIST + */ + +/********************* + * INCLUDES + *********************/ + +#include "music_player.h" +#include <stdlib.h> +#include <netutils/cJSON.h> +#include <time.h> + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/* Init functions */ +static void read_configs(void); +static bool init_resource(void); +static void reload_music_config(void); +static void app_create_error_page(void); +static void app_create_main_page(void); +static void app_create_top_layer(void); + +/* Timer starting functions */ +static void app_start_updating_date_time(void); + +/* Album operations */ +static int32_t app_get_album_index(album_info_t* album); +static void app_switch_to_album(int index); + +/* Album operations */ +static void app_set_play_status(play_status_t status); +static void app_set_playback_time(uint32_t current_time); +static void app_set_volume(uint16_t volume); + +/* UI refresh functions */ +static void app_refresh_album_info(void); +static void app_refresh_date_time(void); +static void app_refresh_play_status(void); +static void app_refresh_playback_progress(void); +static void app_refresh_playlist(void); +static void app_refresh_volume_bar(void); +static void app_refresh_volume_countdown_timer(void); + +/* Event handler functions */ +static void app_audio_event_handler(lv_event_t* e); +static void app_play_status_event_handler(lv_event_t* e); +static void app_playlist_btn_event_handler(lv_event_t* e); +static void app_playlist_event_handler(lv_event_t* e); +static void app_switch_album_event_handler(lv_event_t* e); +static void app_volume_bar_event_handler(lv_event_t* e); +static void app_playback_progress_bar_event_handler(lv_event_t* e); + +/* Timer callback functions */ +static void app_refresh_date_time_timer_cb(lv_timer_t* timer); +static void app_playback_progress_update_timer_cb(lv_timer_t* timer); +static void app_volume_bar_countdown_timer_cb(lv_timer_t* timer); + +/********************** + * STATIC VARIABLES + **********************/ + +// clang-format off +struct resource_s R; /**< Resources */ +struct ctx_s C; /**< Context */ +struct conf_s CF; /**< Configuration */ +// clang-format on + +/* Week days mapping */ +const char* WEEK_DAYS[] = { "Sun.", "Mon.", "Tues.", "Wed.", "Thur.", "Fri.", "Sat." }; + +/* Transition properties for the objects */ +const lv_style_prop_t transition_props[] = { + LV_STYLE_OPA, + LV_STYLE_BG_OPA, + LV_STYLE_Y, + LV_STYLE_HEIGHT, + LV_STYLE_PROP_FLAG_NONE +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void app_create(void) +{ + // Init resource and context structure + lv_memzero(&R, sizeof(R)); + lv_memzero(&C, sizeof(C)); + lv_memzero(&CF, sizeof(CF)); + + read_configs(); + +#if WIFI_ENABLED + CF.wifi.conn_delay = 2000000; + wifi_connect(&CF.wifi); +#endif + + C.resource_healthy_check = init_resource(); + + if (!C.resource_healthy_check) { + app_create_error_page(); + return; + } + + app_create_main_page(); + app_set_play_status(PLAY_STATUS_STOP); + app_switch_to_album(0); + app_set_volume(30); + + app_refresh_album_info(); + app_refresh_playlist(); + app_refresh_volume_bar(); + + app_start_updating_date_time(); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static int32_t app_get_album_index(album_info_t* album) +{ + for (int i = 0; i < R.album_count; i++) { + if (album == &R.albums[i]) { + return i; + } + } + return -1; +} + +static void app_set_volume(uint16_t volume) +{ + C.volume = volume; + audio_ctl_set_volume(C.audioctl, C.volume); +} + +static void app_set_play_status(play_status_t status) +{ + C.play_status_prev = C.play_status; + C.play_status = status; + app_refresh_play_status(); +} + +static void app_switch_to_album(int index) +{ + if (R.album_count == 0 || index < 0 || index >= R.album_count || C.current_album == &R.albums[index]) + return; + + C.current_album = &R.albums[index]; + app_refresh_album_info(); + app_refresh_playlist(); + app_set_playback_time(0); + + if (C.play_status == PLAY_STATUS_STOP) { + return; + } + + app_set_play_status(PLAY_STATUS_STOP); + app_set_play_status(PLAY_STATUS_PLAY); +} + +static void app_set_playback_time(uint32_t current_time) +{ + C.current_time = current_time; + + audio_ctl_seek(C.audioctl, C.current_time / 1000); + app_refresh_playback_progress(); +} + +static void app_refresh_date_time(void) +{ + time_t now = time(NULL); + struct tm* timeinfo = localtime(&now); + + char date_str[20]; + strftime(date_str, sizeof(date_str), "%Y.%m.%d", timeinfo); + lv_snprintf(date_str + 10, sizeof(date_str), " %s", WEEK_DAYS[timeinfo->tm_wday]); + lv_label_set_text(R.ui.date, date_str); + + char time_str[9]; + strftime(time_str, sizeof(time_str), "%H:%M", timeinfo); + lv_label_set_text(R.ui.time, time_str); +} + +static void app_refresh_volume_bar(void) +{ + int32_t volume_bar_indic_height = C.volume; + + lv_obj_set_height(R.ui.volume_bar_indic, volume_bar_indic_height); + + lv_obj_refr_size(R.ui.volume_bar_indic); + lv_obj_update_layout(R.ui.volume_bar_indic); + + if (C.volume > 0) { + lv_image_set_src(R.ui.audio, R.images.audio); + } else { + lv_image_set_src(R.ui.audio, R.images.mute); + } +} + +static void app_refresh_album_info(void) +{ + if (C.current_album) { + if (access(C.current_album->cover, F_OK) == 0) + lv_image_set_src(R.ui.album_cover, C.current_album->cover); + else + lv_image_set_src(R.ui.album_cover, R.images.nocover); + lv_label_set_text(R.ui.album_name, C.current_album->name); + lv_label_set_text(R.ui.album_artist, C.current_album->artist); + } +} + +static void app_refresh_play_status(void) +{ + if (C.timers.playback_progress_update == NULL) { + C.timers.playback_progress_update = lv_timer_create(app_playback_progress_update_timer_cb, 1000, NULL); + } + + switch (C.play_status) { + case PLAY_STATUS_STOP: + lv_image_set_src(R.ui.play_btn, R.images.play); + lv_timer_pause(C.timers.playback_progress_update); + if (C.audioctl) { + audio_ctl_stop(C.audioctl); + audio_ctl_uninit_nxaudio(C.audioctl); + C.audioctl = NULL; + } + break; + case PLAY_STATUS_PLAY: + lv_image_set_src(R.ui.play_btn, R.images.pause); + lv_timer_resume(C.timers.playback_progress_update); + if (C.play_status_prev == PLAY_STATUS_PAUSE) + audio_ctl_resume(C.audioctl); + else if (C.play_status_prev == PLAY_STATUS_STOP) { + C.audioctl = audio_ctl_init_nxaudio(C.current_album->path); + audio_ctl_start(C.audioctl); + } + break; + case PLAY_STATUS_PAUSE: + lv_image_set_src(R.ui.play_btn, R.images.play); + lv_timer_pause(C.timers.playback_progress_update); + audio_ctl_pause(C.audioctl); + break; + default: + break; + } +} + +static void app_refresh_playback_progress(void) +{ + uint64_t total_time = C.current_album->total_time; + + if (C.current_time > total_time) { + app_set_play_status(PLAY_STATUS_STOP); + C.current_time = 0; + return; + } + + lv_bar_set_range(R.ui.playback_progress, 0, (int32_t)total_time); + lv_bar_set_value(R.ui.playback_progress, (int32_t)C.current_time, LV_ANIM_ON); + + char buff[256]; + + uint32_t current_time_min = C.current_time / 60000; + uint32_t current_time_sec = (C.current_time % 60000) / 1000; + uint32_t total_time_min = total_time / 60000; + uint32_t total_time_sec = (total_time % 60000) / 1000; + + lv_snprintf(buff, sizeof(buff), "%02d:%02d", current_time_min, current_time_sec); + lv_span_set_text(R.ui.playback_current_time, buff); + lv_snprintf(buff, sizeof(buff), "%02d:%02d", total_time_min, total_time_sec); + lv_span_set_text(R.ui.playback_total_time, buff); +} + +static void app_refresh_playlist(void) +{ + lv_obj_clean(R.ui.playlist); + for (int i = 0; i < R.album_count; i++) { + // LIST ITEM + lv_obj_t* list_item = lv_obj_create(R.ui.playlist); + lv_obj_remove_style_all(list_item); + lv_obj_set_size(list_item, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_margin_all(list_item, 20, LV_PART_MAIN); + lv_obj_set_flex_flow(list_item, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(list_item, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + // ICON + if (C.current_album == &R.albums[i]) { + lv_obj_t* icon = lv_image_create(list_item); + lv_image_set_src(icon, R.images.music); + lv_obj_set_style_bg_opa(icon, LV_OPA_TRANSP, LV_PART_MAIN); + lv_obj_set_style_radius(icon, 16, LV_PART_MAIN); + lv_obj_set_style_margin_right(icon, 12, LV_PART_MAIN); + lv_obj_align(icon, LV_ALIGN_TOP_LEFT, 10, 10); + + C.current_album_related_obj = list_item; + } + + // ALBUM NAME + lv_obj_t* album_info_spangroup = lv_spangroup_create(list_item); + lv_obj_set_size(album_info_spangroup, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_add_flag(album_info_spangroup, LV_OBJ_FLAG_EVENT_BUBBLE); + lv_spangroup_set_overflow(album_info_spangroup, LV_SPAN_OVERFLOW_ELLIPSIS); + + lv_span_t* album_name = lv_spangroup_new_span(album_info_spangroup); + lv_span_set_text(album_name, R.albums[i].name); + lv_obj_set_style_text_font(album_info_spangroup, R.fonts.size_24.normal, LV_PART_MAIN); + lv_obj_set_style_text_color(album_info_spangroup, lv_color_white(), LV_PART_MAIN); + + // ALBUM ARTIST DOT + lv_span_t* album_artist_dot = lv_spangroup_new_span(album_info_spangroup); + lv_span_set_text(album_artist_dot, "·"); + lv_style_set_text_color(&album_artist_dot->style, lv_color_hex(0x878787)); + + // ALBUM ARTIST + lv_span_t* album_artist = lv_spangroup_new_span(album_info_spangroup); + lv_span_set_text(album_artist, R.albums[i].artist); + lv_style_set_text_color(&album_artist->style, lv_color_hex(0x878787)); + + // EVENTS + lv_obj_add_event_cb(list_item, app_playlist_btn_event_handler, LV_EVENT_CLICKED, &R.albums[i]); + } + + if (C.current_album_related_obj) + lv_obj_scroll_to_view(C.current_album_related_obj, LV_ANIM_ON); +} + +static void app_volume_bar_countdown_timer_cb(lv_timer_t* timer) +{ + LV_UNUSED(timer); + lv_obj_set_state(R.ui.volume_bar, LV_STATE_DEFAULT, true); + lv_obj_set_state(R.ui.volume_bar, LV_STATE_USER_1, false); +} + +static void app_playback_progress_update_timer_cb(lv_timer_t* timer) +{ + LV_UNUSED(timer); + + C.current_time = audio_ctl_get_position(C.audioctl) * 1000; + app_refresh_playback_progress(); +} + +static void app_refresh_date_time_timer_cb(lv_timer_t* timer) +{ + LV_UNUSED(timer); + + app_refresh_date_time(); +} + +static void app_refresh_volume_countdown_timer(void) +{ + if (C.timers.volume_bar_countdown) { + lv_timer_set_repeat_count(C.timers.volume_bar_countdown, 1); + lv_timer_reset(C.timers.volume_bar_countdown); + lv_timer_resume(C.timers.volume_bar_countdown); + } else { + C.timers.volume_bar_countdown = lv_timer_create(app_volume_bar_countdown_timer_cb, 3000, NULL); + lv_timer_set_auto_delete(C.timers.volume_bar_countdown, false); + } +} + +static void app_playlist_event_handler(lv_event_t* e) +{ + LV_UNUSED(e); + + lv_obj_t* top_layer = lv_layer_top(); + + int32_t screen_height = lv_obj_get_height(lv_screen_active()); + + if (lv_obj_has_state(R.ui.playlist_base, LV_STATE_USER_1)) { + lv_obj_set_style_y(R.ui.playlist_base, screen_height, LV_STATE_DEFAULT); + lv_obj_set_state(R.ui.playlist_base, LV_STATE_DEFAULT, true); + lv_obj_set_state(R.ui.playlist_base, LV_STATE_USER_1, false); + lv_obj_set_state(top_layer, LV_STATE_DEFAULT, true); + lv_obj_set_state(top_layer, LV_STATE_USER_1, false); + } else { + lv_obj_set_state(R.ui.playlist_base, LV_STATE_DEFAULT, false); + lv_obj_set_state(R.ui.playlist_base, LV_STATE_USER_1, true); + lv_obj_set_state(top_layer, LV_STATE_DEFAULT, false); + lv_obj_set_state(top_layer, LV_STATE_USER_1, true); + } +} + +static void app_volume_bar_event_handler(lv_event_t* e) +{ + lv_event_code_t code = lv_event_get_code(e); + + if (!(code == LV_EVENT_PRESSED || code == LV_EVENT_PRESSING || code == LV_EVENT_PRESS_LOST)) { + return; + } + + lv_point_t point; + lv_indev_t* indev = lv_indev_active(); + lv_indev_get_vect(indev, &point); + + int32_t volume_bar_height = lv_obj_get_height(R.ui.volume_bar); + int32_t volume_bar_indic_height = lv_obj_get_height(R.ui.volume_bar_indic); + + if (volume_bar_indic_height < 0) { + volume_bar_indic_height = 0; + } else if (volume_bar_indic_height > volume_bar_height) { + volume_bar_indic_height = volume_bar_height; + } + + int32_t volume = volume_bar_indic_height - point.y; + if (volume < 0) + volume = 0; + + app_set_volume(volume); + + app_refresh_volume_bar(); + app_refresh_volume_countdown_timer(); +} + +static void app_audio_event_handler(lv_event_t* e) +{ + LV_UNUSED(e); + + if (lv_obj_has_state(R.ui.volume_bar, LV_STATE_USER_1)) { + lv_obj_set_state(R.ui.volume_bar, LV_STATE_DEFAULT, true); + lv_obj_set_state(R.ui.volume_bar, LV_STATE_USER_1, false); + } else { + lv_obj_set_state(R.ui.volume_bar, LV_STATE_DEFAULT, false); + lv_obj_set_state(R.ui.volume_bar, LV_STATE_USER_1, true); + app_refresh_volume_countdown_timer(); + } +} + +static void app_playlist_btn_event_handler(lv_event_t* e) +{ + album_info_t* album_info = (album_info_t*)lv_event_get_user_data(e); + + int32_t index = app_get_album_index(album_info); + if (index >= 0) { + app_switch_to_album(index); + app_playlist_event_handler(NULL); + } +} + +static void app_switch_album_event_handler(lv_event_t* e) +{ + switch_album_mode_t direction = (switch_album_mode_t)(lv_uintptr_t)lv_event_get_user_data(e); + + int32_t album_index = app_get_album_index(C.current_album); + if (album_index < 0) { + return; + } + + switch (direction) { + case SWITCH_ALBUM_MODE_PREV: + album_index--; + break; + case SWITCH_ALBUM_MODE_NEXT: + album_index++; + break; + default: + break; + } + + album_index = (album_index + R.album_count) % R.album_count; + + app_switch_to_album(album_index); +} + +static void app_play_status_event_handler(lv_event_t* e) +{ + LV_UNUSED(e); + + switch (C.play_status) { + case PLAY_STATUS_STOP: + app_set_play_status(PLAY_STATUS_PLAY); + break; + case PLAY_STATUS_PLAY: + app_set_play_status(PLAY_STATUS_PAUSE); + break; + case PLAY_STATUS_PAUSE: + app_set_play_status(PLAY_STATUS_PLAY); + break; + default: + break; + } +} + +static void app_playback_progress_bar_event_handler(lv_event_t* e) +{ + lv_event_code_t code = lv_event_get_code(e); + + switch (code) { + case LV_EVENT_LONG_PRESSED: + lv_obj_set_height(R.ui.playback_progress, 8); + lv_obj_set_style_margin_ver(R.ui.playback_progress, 0, LV_PART_MAIN); + lv_obj_set_state(R.ui.playback_progress, LV_STATE_CHECKED, true); + break; + case LV_EVENT_PRESS_LOST: + case LV_EVENT_RELEASED: + lv_obj_set_height(R.ui.playback_progress, 4); + lv_obj_set_style_margin_ver(R.ui.playback_progress, 2, LV_PART_MAIN); + lv_obj_set_state(R.ui.playback_progress, LV_STATE_CHECKED, false); + break; + case LV_EVENT_PRESSING: { + if (lv_obj_has_state(R.ui.playback_progress, LV_STATE_CHECKED)) { + lv_point_t point; + lv_point_t vec; + lv_indev_t* indev = lv_indev_active(); + lv_indev_get_point(indev, &point); + lv_indev_get_vect(indev, &vec); + + if (point.y <= 0) { + return; + } + + int32_t screen_height = lv_obj_get_height(lv_screen_active()); + int32_t screen_width = lv_obj_get_width(lv_screen_active()); + int32_t level_height = screen_height / 10; + int32_t level = point.y / level_height + 1; + + uint64_t total_time = C.current_album ? C.current_album->total_time : 0; + + C.current_time += vec.x * level * (int32_t)total_time / screen_width / 10; + app_set_playback_time(C.current_time); + } + break; + } + default: + break; + } +} + +static bool init_resource(void) +{ + // Fonts + R.fonts.size_16.normal = lv_freetype_font_create(FONTS_ROOT "/MiSans-Normal.ttf", LV_FREETYPE_FONT_RENDER_MODE_BITMAP, 16, LV_FREETYPE_FONT_STYLE_NORMAL); + R.fonts.size_22.bold = lv_freetype_font_create(FONTS_ROOT "/MiSans-Semibold.ttf", LV_FREETYPE_FONT_RENDER_MODE_BITMAP, 22, LV_FREETYPE_FONT_STYLE_NORMAL); + R.fonts.size_24.normal = lv_freetype_font_create(FONTS_ROOT "/MiSans-Normal.ttf", LV_FREETYPE_FONT_RENDER_MODE_BITMAP, 24, LV_FREETYPE_FONT_STYLE_NORMAL); + R.fonts.size_28.normal = lv_freetype_font_create(FONTS_ROOT "/MiSans-Semibold.ttf", LV_FREETYPE_FONT_RENDER_MODE_BITMAP, 38, LV_FREETYPE_FONT_STYLE_NORMAL); + R.fonts.size_60.bold = lv_freetype_font_create(FONTS_ROOT "/MiSans-Semibold.ttf", LV_FREETYPE_FONT_RENDER_MODE_BITMAP, 60, LV_FREETYPE_FONT_STYLE_NORMAL); + + if (R.fonts.size_16.normal == NULL || + R.fonts.size_22.bold == NULL || + R.fonts.size_24.normal == NULL || + R.fonts.size_28.normal == NULL || + R.fonts.size_60.bold == NULL ) { + return false; + } + + // Styles + lv_style_init(&R.styles.button_default); + lv_style_init(&R.styles.button_pressed); + lv_style_set_opa(&R.styles.button_default, LV_OPA_COVER); + lv_style_set_opa(&R.styles.button_pressed, LV_OPA_70); + + // transition animation + lv_style_transition_dsc_init(&R.styles.transition_dsc, transition_props, &lv_anim_path_ease_in_out, 300, 0, NULL); + lv_style_transition_dsc_init(&R.styles.button_transition_dsc, transition_props, &lv_anim_path_ease_in_out, 80, 0, NULL); + lv_style_set_transition(&R.styles.button_default, &R.styles.button_transition_dsc); + lv_style_set_transition(&R.styles.button_pressed, &R.styles.button_transition_dsc); + + // images + R.images.playlist = ICONS_ROOT "/playlist.png"; + R.images.previous = ICONS_ROOT "/previous.png"; + R.images.play = ICONS_ROOT "/play.png"; + R.images.pause = ICONS_ROOT "/pause.png"; + R.images.next = ICONS_ROOT "/next.png"; + R.images.audio = ICONS_ROOT "/audio.png"; + R.images.mute = ICONS_ROOT "/mute.png"; + R.images.music = ICONS_ROOT "/music.png"; + R.images.nocover = ICONS_ROOT "/nocover.png"; + + // albums + reload_music_config(); + + return true; +} + +static void app_create_top_layer(void) +{ + lv_obj_t* top_layer = lv_layer_top(); + lv_obj_set_scroll_dir(top_layer, LV_DIR_NONE); + lv_obj_set_style_bg_color(top_layer, lv_color_black(), LV_PART_MAIN); + lv_obj_set_style_bg_opa(top_layer, LV_OPA_COVER, LV_STATE_USER_1); + lv_obj_set_style_bg_opa(top_layer, LV_OPA_0, LV_STATE_DEFAULT); + lv_obj_set_style_transition(top_layer, &R.styles.transition_dsc, LV_STATE_DEFAULT); + lv_obj_set_style_transition(top_layer, &R.styles.transition_dsc, LV_STATE_USER_1); + + // VOLUME BAR + R.ui.volume_bar = lv_obj_create(top_layer); + lv_obj_remove_style_all(R.ui.volume_bar); + lv_obj_set_size(R.ui.volume_bar, 60, 180); + lv_obj_set_style_bg_color(R.ui.volume_bar, lv_color_hex(0x444444), LV_PART_MAIN); + lv_obj_set_style_bg_opa(R.ui.volume_bar, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_opa(R.ui.volume_bar, LV_OPA_0, LV_STATE_DEFAULT); + lv_obj_set_style_opa(R.ui.volume_bar, LV_OPA_COVER, LV_STATE_USER_1); + lv_obj_set_style_border_width(R.ui.volume_bar, 0, LV_PART_MAIN); + lv_obj_set_style_radius(R.ui.volume_bar, 16, LV_PART_MAIN); + lv_obj_set_style_clip_corner(R.ui.volume_bar, true, LV_PART_MAIN); + lv_obj_align(R.ui.volume_bar, LV_ALIGN_BOTTOM_RIGHT, -45, -95); + lv_obj_set_style_transition(R.ui.volume_bar, &R.styles.transition_dsc, LV_STATE_DEFAULT); + + R.ui.volume_bar_indic = lv_obj_create(R.ui.volume_bar); + lv_obj_remove_style_all(R.ui.volume_bar_indic); + lv_obj_set_style_bg_color(R.ui.volume_bar_indic, lv_color_white(), LV_PART_MAIN); + lv_obj_set_style_bg_opa(R.ui.volume_bar_indic, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_size(R.ui.volume_bar_indic, LV_PCT(100), 40); + lv_obj_align(R.ui.volume_bar_indic, LV_ALIGN_BOTTOM_MID, 0, 0); + + // PLAYLIST GROUP + R.ui.playlist_base = lv_obj_create(top_layer); + lv_obj_remove_style_all(R.ui.playlist_base); + lv_obj_set_size(R.ui.playlist_base, LV_PCT(100), LV_PCT(100)); + lv_obj_set_style_y(R.ui.playlist_base, 480, LV_STATE_DEFAULT); + lv_obj_set_style_y(R.ui.playlist_base, 0, LV_STATE_USER_1); + lv_obj_set_flex_flow(R.ui.playlist_base, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_top(R.ui.playlist_base, 60, LV_PART_MAIN); + lv_obj_set_style_pad_left(R.ui.playlist_base, 10, LV_PART_MAIN); + lv_obj_set_style_pad_right(R.ui.playlist_base, 10, LV_PART_MAIN); + lv_obj_set_style_pad_bottom(R.ui.playlist_base, 10, LV_PART_MAIN); + lv_obj_set_flex_align(R.ui.playlist_base, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + lv_obj_t* playlist_group = lv_obj_create(R.ui.playlist_base); + lv_obj_remove_style_all(playlist_group); + lv_obj_set_style_bg_color(playlist_group, lv_color_hex(0x1E1E20), LV_PART_MAIN); + lv_obj_set_style_bg_opa(playlist_group, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_size(playlist_group, LV_PCT(100), LV_PCT(100)); + lv_obj_set_style_radius(playlist_group, 16, LV_PART_MAIN); + lv_obj_set_style_clip_corner(playlist_group, true, LV_PART_MAIN); + lv_obj_set_flex_flow(playlist_group, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(playlist_group, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + // TITLE + lv_obj_t* title = lv_label_create(playlist_group); + lv_label_set_text(title, "PLAYLIST"); + lv_obj_set_style_text_font(title, R.fonts.size_28.normal, LV_PART_MAIN); + lv_obj_set_style_text_color(title, lv_color_white(), LV_PART_MAIN); + lv_obj_set_style_margin_all(title, 15, LV_PART_MAIN); + + // LIST + lv_obj_t* list = lv_obj_create(playlist_group); + R.ui.playlist = list; + lv_obj_set_size(list, LV_PCT(100), LV_PCT(100)); + lv_obj_set_style_border_width(list, 0, LV_PART_MAIN); + lv_obj_set_style_bg_color(list, lv_color_hex(0x1E1E20), LV_PART_MAIN); + lv_obj_set_style_bg_opa(list, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_radius(list, 0, LV_PART_MAIN); + lv_obj_set_flex_grow(list, 1); + lv_obj_set_flex_flow(list, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(list, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + // EVENTS + lv_obj_add_event_cb(R.ui.playlist_base, app_playlist_event_handler, LV_EVENT_CLICKED, NULL); + lv_obj_set_style_transition(R.ui.playlist_base, &R.styles.transition_dsc, LV_STATE_DEFAULT); + lv_obj_set_style_transition(R.ui.playlist_base, &R.styles.transition_dsc, LV_STATE_USER_1); + + lv_obj_add_flag(R.ui.volume_bar_indic, LV_OBJ_FLAG_EVENT_BUBBLE); + lv_obj_add_event_cb(R.ui.volume_bar, app_volume_bar_event_handler, LV_EVENT_ALL, NULL); +} + +static void app_create_error_page(void) +{ + lv_obj_t* root = lv_screen_active(); + lv_obj_t* label = lv_label_create(root); + lv_obj_set_width(label, LV_PCT(80)); + lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP); + lv_label_set_text(label, "Resource loading failed. \nPlease check the device and \nread the document for more details."); + lv_obj_set_style_text_font(label, &lv_font_montserrat_32, LV_PART_MAIN); + lv_obj_center(label); +} + +static void app_create_main_page(void) +{ + lv_obj_t* root = lv_screen_active(); + + lv_obj_set_style_bg_color(root, lv_color_black(), LV_PART_MAIN); + lv_obj_set_style_border_width(root, 0, LV_PART_MAIN); + lv_obj_set_flex_flow(root, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(root, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_all(root, 20, LV_PART_MAIN); + + // TIME GROUP + lv_obj_t* time_group = lv_obj_create(root); + lv_obj_set_size(time_group, LV_PCT(100), LV_PCT(50)); + lv_obj_set_style_bg_color(time_group, lv_color_black(), LV_PART_MAIN); + lv_obj_set_style_border_width(time_group, 0, LV_PART_MAIN); + lv_obj_set_style_radius(time_group, 0, LV_PART_MAIN); + lv_obj_set_flex_grow(time_group, 1); + lv_obj_set_flex_flow(time_group, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(time_group, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + // TIME + lv_obj_t* time = lv_label_create(time_group); + R.ui.time = time; + lv_label_set_text(time, "13:14"); + lv_obj_set_style_text_font(time, R.fonts.size_60.bold, LV_PART_MAIN); + lv_obj_set_style_text_color(time, lv_color_white(), LV_PART_MAIN); + + // DATE + lv_obj_t* date = lv_label_create(time_group); + R.ui.date = date; + lv_label_set_text(date, "2024.03.22 Fri."); + lv_obj_set_style_text_font(date, R.fonts.size_24.normal, LV_PART_MAIN); + lv_obj_set_style_text_color(date, lv_color_hex(0x878787), LV_PART_MAIN); + + // PLAYER GROUP + lv_obj_t* player_group = lv_obj_create(root); + R.ui.player_group = player_group; + lv_obj_set_size(player_group, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_bg_color(player_group, lv_color_hex(0x1E1E20), LV_PART_MAIN); + lv_obj_set_style_border_width(player_group, 0, LV_PART_MAIN); + lv_obj_set_style_radius(player_group, 16, LV_PART_MAIN); + lv_obj_set_style_pad_all(player_group, 20, LV_PART_MAIN); + lv_obj_set_flex_flow(player_group, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(player_group, LV_FLEX_ALIGN_SPACE_AROUND, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + // ALBUM GROUP + lv_obj_t* album_group = lv_obj_create(player_group); + lv_obj_remove_style_all(album_group); + lv_obj_set_size(album_group, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_flex_flow(album_group, LV_FLEX_FLOW_ROW, LV_PART_MAIN); + lv_obj_set_flex_align(album_group, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + // ALBUM PICTURE + lv_obj_t* album_cover = lv_obj_create(album_group); + lv_obj_t* album_cover_img = lv_image_create(album_cover); + R.ui.album_cover = album_cover_img; + lv_obj_remove_style_all(album_cover); + lv_obj_set_size(album_cover, 80, 80); + lv_obj_set_size(album_cover_img, 80, 80); + lv_obj_set_style_radius(album_cover, 12, LV_PART_MAIN); + lv_obj_set_style_radius(album_cover_img, 12, LV_PART_MAIN); + lv_obj_set_style_clip_corner(album_cover, true, LV_PART_MAIN); + lv_obj_set_style_clip_corner(album_cover_img, true, LV_PART_MAIN); + lv_image_set_align(album_cover_img, LV_IMAGE_ALIGN_STRETCH); + lv_image_set_src(album_cover_img, R.images.nocover); + + // ALBUM INFO + lv_obj_t* album_info = lv_obj_create(album_group); + lv_obj_remove_style_all(album_info); + lv_obj_remove_flag(album_info, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_size(album_info, LV_SIZE_CONTENT, 80); + lv_obj_set_style_pad_left(album_info, 20, LV_PART_MAIN); + lv_obj_set_flex_grow(album_info, 1); + lv_obj_set_style_flex_flow(album_info, LV_FLEX_FLOW_COLUMN, LV_PART_MAIN); + lv_obj_set_flex_align(album_info, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + + // ALBUM NAME + lv_obj_t* album_name = lv_label_create(album_info); + R.ui.album_name = album_name; + lv_label_set_long_mode(album_name, LV_LABEL_LONG_SCROLL); + lv_obj_set_width(album_name, LV_PCT(100)); + lv_obj_set_style_text_align(album_name, LV_TEXT_ALIGN_LEFT, LV_PART_MAIN); + lv_obj_set_style_text_font(album_name, R.fonts.size_22.bold, LV_PART_MAIN); + lv_obj_set_style_text_color(album_name, lv_color_white(), LV_PART_MAIN); + lv_label_set_text(album_name, "No Album"); + + // ALBUM ARTIST + lv_obj_t* album_artist = lv_label_create(album_info); + R.ui.album_artist = album_artist; + lv_label_set_long_mode(album_name, LV_LABEL_LONG_SCROLL); + lv_obj_set_width(album_artist, LV_PCT(100)); + lv_obj_set_style_text_align(album_artist, LV_TEXT_ALIGN_LEFT, LV_PART_MAIN); + lv_obj_set_style_text_font(album_artist, R.fonts.size_22.bold, LV_PART_MAIN); + lv_obj_set_style_text_color(album_artist, lv_color_hex3(0x666), LV_PART_MAIN); + lv_label_set_text(album_artist, "No Artist"); + + // PROGRESS BAR GROUP + lv_obj_t* progress_group = lv_obj_create(player_group); + R.ui.playback_group = progress_group; + lv_obj_remove_style_all(progress_group); + lv_obj_set_size(progress_group, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_margin_top(progress_group, -16, LV_PART_MAIN); + lv_obj_set_style_flex_flow(progress_group, LV_FLEX_FLOW_COLUMN, LV_PART_MAIN); + lv_obj_set_flex_align(progress_group, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_END, LV_FLEX_ALIGN_CENTER); + + lv_obj_t* progress_time_spangroup = lv_spangroup_create(progress_group); + lv_span_t* current_time_span = lv_spangroup_new_span(progress_time_spangroup); + lv_span_t* slash_span = lv_spangroup_new_span(progress_time_spangroup); + lv_span_t* total_time_span = lv_spangroup_new_span(progress_time_spangroup); + R.ui.playback_current_time = current_time_span; + R.ui.playback_total_time = total_time_span; + lv_span_set_text(current_time_span, "00:00"); + lv_span_set_text(slash_span, "/"); + lv_span_set_text(total_time_span, "00:00"); + lv_obj_set_style_text_font(progress_time_spangroup, R.fonts.size_16.normal, LV_PART_MAIN); + lv_obj_set_style_text_color(progress_time_spangroup, lv_color_white(), LV_PART_MAIN); + lv_style_set_text_color(&slash_span->style, lv_color_hex(0x666666)); + lv_style_set_text_color(&total_time_span->style, lv_color_hex(0x666666)); + + lv_obj_t* progress_bar = lv_bar_create(progress_group); + R.ui.playback_progress = progress_bar; + lv_obj_set_size(progress_bar, LV_PCT(100), 4); + lv_obj_add_flag(progress_bar, LV_OBJ_FLAG_EVENT_BUBBLE); + lv_obj_set_style_margin_ver(progress_bar, 2, LV_PART_MAIN); + lv_obj_set_style_bg_color(progress_bar, lv_color_white(), LV_PART_INDICATOR); + lv_obj_set_style_bg_color(progress_bar, lv_color_hex(0x3A3A3C), LV_PART_MAIN); + lv_obj_set_style_bg_opa(progress_bar, LV_OPA_COVER, LV_PART_MAIN); + + // CONTROL GROUP + lv_obj_t* control_group = lv_obj_create(player_group); + lv_obj_remove_style_all(control_group); + lv_obj_set_size(control_group, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_flex_flow(control_group, LV_FLEX_FLOW_ROW, LV_PART_MAIN); + lv_obj_set_flex_align(control_group, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + // PLAYLIST + lv_obj_t* playlist = lv_button_create(control_group); + lv_obj_t* playlist_image = lv_image_create(playlist); + lv_obj_remove_style_all(playlist); + lv_image_set_src(playlist_image, R.images.playlist); + lv_obj_set_size(playlist_image, 66, 66); + lv_obj_add_style(playlist, &R.styles.button_default, LV_STATE_DEFAULT); + lv_obj_add_style(playlist, &R.styles.button_pressed, LV_STATE_PRESSED); + + // PREVIOUS + lv_obj_t* previous = lv_button_create(control_group); + lv_obj_t* previous_image = lv_image_create(previous); + lv_obj_remove_style_all(previous); + lv_image_set_src(previous_image, R.images.previous); + lv_obj_set_size(previous_image, 66, 66); + lv_obj_add_style(previous, &R.styles.button_default, LV_STATE_DEFAULT); + lv_obj_add_style(previous, &R.styles.button_pressed, LV_STATE_PRESSED); + + // PLAY/PAUSE + lv_obj_t* play_pause = lv_button_create(control_group); + lv_obj_t* play_pause_image = lv_image_create(play_pause); + R.ui.play_btn = play_pause_image; + lv_obj_remove_style_all(play_pause); + lv_image_set_src(play_pause_image, R.images.play); + lv_obj_set_size(play_pause_image, 66, 66); + lv_obj_add_style(play_pause, &R.styles.button_default, LV_STATE_DEFAULT); + lv_obj_add_style(play_pause, &R.styles.button_pressed, LV_STATE_PRESSED); + + // NEXT + lv_obj_t* next = lv_button_create(control_group); + lv_obj_t* next_image = lv_image_create(next); + lv_obj_remove_style_all(next); + lv_image_set_src(next_image, R.images.next); + lv_obj_set_size(next_image, 66, 66); + lv_obj_add_style(next, &R.styles.button_default, LV_STATE_DEFAULT); + lv_obj_add_style(next, &R.styles.button_pressed, LV_STATE_PRESSED); + + // AUDIO + lv_obj_t* audio = lv_button_create(control_group); + lv_obj_t* audio_image = lv_image_create(audio); + R.ui.audio = audio_image; + lv_obj_remove_style_all(audio); + lv_obj_set_size(audio_image, 66, 66); + lv_obj_add_style(audio, &R.styles.button_default, LV_STATE_DEFAULT); + lv_obj_add_style(audio, &R.styles.button_pressed, LV_STATE_PRESSED); + + app_create_top_layer(); + + lv_obj_add_event_cb(playlist, app_playlist_event_handler, LV_EVENT_CLICKED, NULL); + lv_obj_add_event_cb(audio, app_audio_event_handler, LV_EVENT_CLICKED, NULL); + lv_obj_add_event_cb(play_pause, app_play_status_event_handler, LV_EVENT_CLICKED, NULL); + lv_obj_add_event_cb(previous, app_switch_album_event_handler, LV_EVENT_CLICKED, (lv_uintptr_t*)SWITCH_ALBUM_MODE_PREV); + lv_obj_add_event_cb(next, app_switch_album_event_handler, LV_EVENT_CLICKED, (lv_uintptr_t*)SWITCH_ALBUM_MODE_NEXT); + lv_obj_add_event_cb(progress_group, app_playback_progress_bar_event_handler, LV_EVENT_ALL, NULL); +} + +static void app_start_updating_date_time(void) +{ + lv_timer_t* timer = lv_timer_create(app_refresh_date_time_timer_cb, 1000, NULL); + lv_timer_ready(timer); +} + +static void read_configs(void) +{ + uint32_t file_size; + lv_fs_file_t file; + lv_fs_open(&file, RES_ROOT "/config.json", LV_FS_MODE_RD); + + lv_fs_seek(&file, 0, LV_FS_SEEK_END); + lv_fs_tell(&file, &file_size); + lv_fs_seek(&file, 0, LV_FS_SEEK_SET); + + char* buff = lv_malloc(file_size); + lv_fs_read(&file, buff, file_size, NULL); + + const char* json_string = buff; + + cJSON* json = cJSON_Parse(json_string); + if (json == NULL) { + const char* error_ptr = cJSON_GetErrorPtr(); + if (error_ptr != NULL) { + LV_LOG_ERROR("parse error: %p", error_ptr); + } + lv_free(buff); + return; + } + +#if WIFI_ENABLED + cJSON* wifi_object = cJSON_GetObjectItem(json, "wifi"); + + const char* ssid = cJSON_GetStringValue(cJSON_GetObjectItem(wifi_object, "ssid")); + const char* pswd = cJSON_GetStringValue(cJSON_GetObjectItem(wifi_object, "pswd")); + const int version = cJSON_GetNumberValue(cJSON_GetObjectItem(wifi_object, "wpa_ver")); + + lv_strcpy(CF.wifi.ssid, ssid); + lv_strcpy(CF.wifi.pswd, pswd); + CF.wifi.ver_flag = version; +#endif + + cJSON_Delete(json); + + lv_free(buff); +} + +static void reload_music_config(void) +{ + + /* Clear previous music config */ + + for (int i = 0; i < R.album_count; i++) { + lv_free((void*)R.albums[i].name); + lv_free((void*)R.albums[i].artist); + } + + lv_free(R.albums); + R.album_count = 0; + + /* Load music config */ + uint32_t file_size; + lv_fs_file_t file; + lv_fs_open(&file, MUSICS_ROOT "/manifest.json", LV_FS_MODE_RD); + + lv_fs_seek(&file, 0, LV_FS_SEEK_END); + lv_fs_tell(&file, &file_size); + lv_fs_seek(&file, 0, LV_FS_SEEK_SET); + + char* buff = lv_malloc(file_size); + lv_fs_read(&file, buff, file_size, NULL); + + const char* json_string = buff; + + cJSON* json = cJSON_Parse(json_string); + if (json == NULL) { + const char* error_ptr = cJSON_GetErrorPtr(); + if (error_ptr != NULL) { + LV_LOG_ERROR("parse error: %p", error_ptr); + } + lv_free(buff); + return; + } + + cJSON* musics_object = cJSON_GetObjectItem(json, "musics"); + + if (musics_object == NULL) { + lv_free(buff); + return; + } + + R.album_count = cJSON_GetArraySize(musics_object); + R.albums = lv_malloc_zeroed(R.album_count * sizeof(album_info_t)); + + for (int i = 0; i < R.album_count; i++) { + cJSON* music_object = cJSON_GetArrayItem(musics_object, i); + + const char* path = cJSON_GetStringValue(cJSON_GetObjectItem(music_object, "path")); + const char* name = cJSON_GetStringValue(cJSON_GetObjectItem(music_object, "name")); + const char* artist = cJSON_GetStringValue(cJSON_GetObjectItem(music_object, "artist")); + const char* cover = cJSON_GetStringValue(cJSON_GetObjectItem(music_object, "cover")); + const double total_time_double = cJSON_GetNumberValue(cJSON_GetObjectItem(music_object, "total_time")); + const char* color_str = cJSON_GetStringValue(cJSON_GetObjectItem(music_object, "color")); + + uint64_t total_time = (uint64_t)total_time_double; + uint32_t color_int = strtoul(color_str + 1, NULL, 16); + + if (total_time == 0) + total_time = 1; + + lv_color_t color = lv_color_hex(color_int); + + lv_snprintf(R.albums[i].path, sizeof(R.albums[i].path), "%s/%s", MUSICS_ROOT, path); + lv_snprintf(R.albums[i].cover, sizeof(R.albums[i].cover), "%s/%s", MUSICS_ROOT, cover); + R.albums[i].name = lv_strdup(name); + R.albums[i].artist = lv_strdup(artist); + R.albums[i].total_time = total_time; + R.albums[i].color = color; + + LV_LOG_USER("Album %d: %s - %s | %s %s %llu", i, R.albums[i].name, R.albums[i].artist, R.albums[i].path, R.albums[i].cover, total_time); + } + + cJSON_Delete(json); + + lv_free(buff); +} diff --git a/music_player/music_player.h b/music_player/music_player.h new file mode 100644 index 0000000000000000000000000000000000000000..dccd1ce0ac3b696dfd74636ac346b5de9e9cd324 --- /dev/null +++ b/music_player/music_player.h @@ -0,0 +1,131 @@ +// +// Created by BenignX on 2024/3/21. +// + +#ifndef LVGL_APP_H +#define LVGL_APP_H + +#include "audio_ctl.h" +#include "lvgl.h" +#include "wifi.h" + +#define RES_ROOT CONFIG_LVX_MUSIC_PLAYER_DATA_ROOT "/res" +#define FONTS_ROOT RES_ROOT "/fonts" +#define ICONS_ROOT RES_ROOT "/icons" +#define MUSICS_ROOT RES_ROOT "/musics" + +typedef struct _album_info_t { + const char* name; + const char* artist; + char path[LV_FS_MAX_PATH_LENGTH]; + char cover[LV_FS_MAX_PATH_LENGTH]; + uint64_t total_time; /**< in milliseconds */ + lv_color_t color; +} album_info_t; + +typedef enum _switch_album_mode_t { + SWITCH_ALBUM_MODE_PREV, + SWITCH_ALBUM_MODE_NEXT, +} switch_album_mode_t; + +typedef enum _play_status_t { + PLAY_STATUS_STOP, + PLAY_STATUS_PLAY, + PLAY_STATUS_PAUSE, +} play_status_t; + +struct resource_s { + struct { + lv_obj_t* time; + lv_obj_t* date; + + lv_obj_t* player_group; + + lv_obj_t* volume_bar; + lv_obj_t* volume_bar_indic; + lv_obj_t* audio; + lv_obj_t* playlist_base; + + lv_obj_t* album_cover; + lv_obj_t* album_name; + lv_obj_t* album_artist; + + lv_obj_t* play_btn; + lv_obj_t* playback_group; + lv_obj_t* playback_progress; + lv_span_t* playback_current_time; + lv_span_t* playback_total_time; + + lv_obj_t* playlist; + } ui; + + struct { + struct { + lv_font_t* normal; + } size_16; + struct { + lv_font_t* bold; + } size_22; + struct { + lv_font_t* normal; + } size_24; + struct { + lv_font_t* normal; + } size_28; + struct { + lv_font_t* bold; + } size_60; + } fonts; + + struct { + lv_style_t button_default; + lv_style_t button_pressed; + lv_style_transition_dsc_t button_transition_dsc; + lv_style_transition_dsc_t transition_dsc; + } styles; + + struct { + const char* playlist; + const char* previous; + const char* play; + const char* pause; + const char* next; + const char* audio; + const char* mute; + const char* music; + const char* nocover; + } images; + + album_info_t* albums; + uint8_t album_count; +}; + +struct ctx_s { + bool resource_healthy_check; + + album_info_t* current_album; + lv_obj_t* current_album_related_obj; + + uint16_t volume; + + play_status_t play_status_prev; + play_status_t play_status; + uint64_t current_time; + + struct { + lv_timer_t* volume_bar_countdown; + lv_timer_t* playback_progress_update; + } timers; + + audioctl_s* audioctl; +}; + +struct conf_s { +#if WIFI_ENABLED + wifi_conf_t wifi; +#endif +}; + +void app_create(void); + +#endif // LVGL_APP_H diff --git a/music_player/music_player_main.c b/music_player/music_player_main.c new file mode 100644 index 0000000000000000000000000000000000000000..0dce2f5013b3533e14f641a81f5c8ec0b8e7d0fd --- /dev/null +++ b/music_player/music_player_main.c @@ -0,0 +1,63 @@ +// include NuttX headers +#include <nuttx/config.h> +#include <unistd.h> +#include <uv.h> + +// include lvgl headers +#include <lvgl/lvgl.h> + +static void lv_nuttx_uv_loop(uv_loop_t* loop, lv_nuttx_result_t* result) +{ + lv_nuttx_uv_t uv_info; + void* data; + + uv_loop_init(loop); + + lv_memset(&uv_info, 0, sizeof(uv_info)); + uv_info.loop = loop; + uv_info.disp = result->disp; + uv_info.indev = result->indev; +#ifdef CONFIG_UINPUT_TOUCH + uv_info.uindev = result->utouch_indev; +#endif + + data = lv_nuttx_uv_init(&uv_info); + uv_run(loop, UV_RUN_DEFAULT); + lv_nuttx_uv_deinit(&data); +} + +#include "music_player.h" + +int main(int argc, FAR char* argv[]) +{ + // init lvgl + lv_nuttx_dsc_t info; + lv_nuttx_result_t result; + uv_loop_t ui_loop; + lv_memset(&ui_loop, 0, sizeof(uv_loop_t)); + + if (lv_is_initialized()) { + LV_LOG_ERROR("LVGL already initialized! aborting."); + return -1; + } + + lv_init(); + + lv_nuttx_dsc_init(&info); + lv_nuttx_init(&info, &result); + + if (result.disp == NULL) { + LV_LOG_ERROR("lv_demos initialization failure!"); + return 1; + } + + app_create(); + + // refresh lvgl ui + lv_nuttx_uv_loop(&ui_loop, &result); + + lv_nuttx_deinit(&result); + lv_deinit(); + + return 0; +} diff --git a/music_player/res/.DS_Store b/music_player/res/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e7970ce24cc0c4a9ab1af62185a61ab4320e2240 Binary files /dev/null and b/music_player/res/.DS_Store differ diff --git a/music_player/res/config.json b/music_player/res/config.json new file mode 100644 index 0000000000000000000000000000000000000000..b760c4852d1c9399dd0b15b7ff5f6b0cc7abe6fa --- /dev/null +++ b/music_player/res/config.json @@ -0,0 +1,6 @@ +{ + "wifi": { + "ssid": "BenignX", + "pswd": "19991123" + } +} diff --git a/music_player/res/fonts/MiSans-Normal.ttf b/music_player/res/fonts/MiSans-Normal.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3199d464141d0599dd1269a3fef4264a829c6da0 Binary files /dev/null and b/music_player/res/fonts/MiSans-Normal.ttf differ diff --git a/music_player/res/fonts/MiSans-Semibold.ttf b/music_player/res/fonts/MiSans-Semibold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..83c1a532632f582550d48bae117d758191ad1800 Binary files /dev/null and b/music_player/res/fonts/MiSans-Semibold.ttf differ diff --git a/music_player/res/icons/album_picture.png b/music_player/res/icons/album_picture.png new file mode 100644 index 0000000000000000000000000000000000000000..5495d183881dc06a0ee0bd0358c4a00c0e4a66d2 Binary files /dev/null and b/music_player/res/icons/album_picture.png differ diff --git a/music_player/res/icons/audio.png b/music_player/res/icons/audio.png new file mode 100644 index 0000000000000000000000000000000000000000..1c99091775d008bfba7d4e1e3dd7dad9d47599c3 Binary files /dev/null and b/music_player/res/icons/audio.png differ diff --git a/music_player/res/icons/music.png b/music_player/res/icons/music.png new file mode 100644 index 0000000000000000000000000000000000000000..be4f163e5d2b0cece03f8ef52b081a3ade804b6e Binary files /dev/null and b/music_player/res/icons/music.png differ diff --git a/music_player/res/icons/mute.png b/music_player/res/icons/mute.png new file mode 100644 index 0000000000000000000000000000000000000000..d5d8b3b5cc2af4d6bb5e0976471af674b8759764 Binary files /dev/null and b/music_player/res/icons/mute.png differ diff --git a/music_player/res/icons/next.png b/music_player/res/icons/next.png new file mode 100644 index 0000000000000000000000000000000000000000..1db3575fb8b4ea7547a9e0c958a6cc62274cd0e2 Binary files /dev/null and b/music_player/res/icons/next.png differ diff --git a/music_player/res/icons/nocover.png b/music_player/res/icons/nocover.png new file mode 100644 index 0000000000000000000000000000000000000000..25282616d4119db1a99f94365254f64105da0acc Binary files /dev/null and b/music_player/res/icons/nocover.png differ diff --git a/music_player/res/icons/pause.png b/music_player/res/icons/pause.png new file mode 100644 index 0000000000000000000000000000000000000000..9fcd00cc4b60533e1ad91f3f5c20e538a04a2a72 Binary files /dev/null and b/music_player/res/icons/pause.png differ diff --git a/music_player/res/icons/play.png b/music_player/res/icons/play.png new file mode 100644 index 0000000000000000000000000000000000000000..f8e7bf726e45a94ac3ee44b9df3b7bbc58bed091 Binary files /dev/null and b/music_player/res/icons/play.png differ diff --git a/music_player/res/icons/playlist.png b/music_player/res/icons/playlist.png new file mode 100644 index 0000000000000000000000000000000000000000..4a1b35a42b02d184def2fee67161b667fd31420c Binary files /dev/null and b/music_player/res/icons/playlist.png differ diff --git a/music_player/res/icons/previous.png b/music_player/res/icons/previous.png new file mode 100644 index 0000000000000000000000000000000000000000..e847a824e020585a156a68a6c794e189922939bd Binary files /dev/null and b/music_player/res/icons/previous.png differ diff --git a/music_player/res/musics/UnamedRhythm.png b/music_player/res/musics/UnamedRhythm.png new file mode 100644 index 0000000000000000000000000000000000000000..5a079cb565f96310773d5c5411d2e1ece948ddfc Binary files /dev/null and b/music_player/res/musics/UnamedRhythm.png differ diff --git a/music_player/res/musics/UnamedRhythm.wav b/music_player/res/musics/UnamedRhythm.wav new file mode 100644 index 0000000000000000000000000000000000000000..7292e7f48f41fbec961c5b27b4f6af3676466550 Binary files /dev/null and b/music_player/res/musics/UnamedRhythm.wav differ diff --git a/music_player/res/musics/manifest.json b/music_player/res/musics/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..d101ea068561fb07f2c17f8b4bb21246cf972b0b --- /dev/null +++ b/music_player/res/musics/manifest.json @@ -0,0 +1,12 @@ +{ + "musics": [ + { + "path": "UnamedRhythm.wav", + "name": "UnamedRhythm", + "artist": "Benign X", + "cover": "UnamedRhythm.png", + "total_time": 12000, + "color": "#114514" + } + ] +} diff --git a/music_player/wifi.c b/music_player/wifi.c new file mode 100644 index 0000000000000000000000000000000000000000..295d83262a558bc7a2dfc09adf8b719c8a144161 --- /dev/null +++ b/music_player/wifi.c @@ -0,0 +1,141 @@ +/** + * @file wifi.c + * Provide wifi connection and network configuration functions + */ + +/********************* + * INCLUDES + *********************/ +#include "wifi.h" + +#if WIFI_ENABLED + +#include <debug.h> +#include <time.h> + +#include "netutils/netlib.h" +#include "netutils/ntpclient.h" + +/********************* + * DEFINES + *********************/ + +#define NET_DEVNAME "wlan0" + + /********************** + * TYPEDEFS + **********************/ + +typedef struct { + struct work_s work; + wifi_conf_t conf; +} wifi_work_conf_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +int wifi_conf_set(FAR wifi_conf_t *usr_conf, + FAR struct wpa_wconfig_s *wifi_conf) +{ + if(strlen(usr_conf->ssid) == 0) { + nerr("ERROR: SSID is NULL\n"); + return -EINVAL; + } + + switch(usr_conf->ver_flag) { + case WPA_VER_NONE: + wifi_conf->auth_wpa = IW_AUTH_WPA_VERSION_DISABLED; + wifi_conf->cipher_mode = IW_AUTH_CIPHER_NONE; + wifi_conf->alg = WPA_ALG_NONE; + break; + + case WPA_VER_1: + wifi_conf->auth_wpa = IW_AUTH_WPA_VERSION_WPA; + wifi_conf->cipher_mode = IW_AUTH_CIPHER_TKIP; + wifi_conf->alg = WPA_ALG_TKIP; + break; + + case WPA_VER_2: + wifi_conf->auth_wpa = IW_AUTH_WPA_VERSION_WPA2; + wifi_conf->cipher_mode = IW_AUTH_CIPHER_CCMP; + wifi_conf->alg = WPA_ALG_CCMP; + break; + + default: + nerr("ERROR: unknown WPA version\n"); + return -EINVAL; + } + + wifi_conf->ifname = NET_DEVNAME; + wifi_conf->sta_mode = IW_MODE_INFRA; + wifi_conf->ssid = usr_conf->ssid; + wifi_conf->passphrase = usr_conf->pswd; + wifi_conf->ssidlen = strlen(usr_conf->ssid); + wifi_conf->phraselen = strlen(usr_conf->pswd); + wifi_conf->bssid = NULL; + wifi_conf->freq = 0; + + return 0; +} + +void wifi_connect_worker(FAR void *arg) +{ + FAR wifi_work_conf_t *wifi_conf = arg; + struct wpa_wconfig_s conf; + + if(wifi_conf_set(&wifi_conf->conf, &conf) < 0) { + nerr("ERROR: wifi parameter is wrong\n"); + goto fail; + } + + if(netlib_ifup(NET_DEVNAME) < 0) { + nerr("ERROR: %s up failed\n", NET_DEVNAME); + goto fail; + } + + if(wpa_driver_wext_associate(&conf) < 0) { + nerr("ERROR: associate failed\n"); + goto fail; + } + + if(netlib_obtain_ipv4addr(NET_DEVNAME) < 0) { + nerr("ERROR: network configuration failed\n"); + goto fail; + } + + ntpc_start(); + +fail: + free(wifi_conf); +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ +void wifi_connect(FAR const wifi_conf_t *conf) +{ + FAR wifi_work_conf_t *wifi_conf = zalloc(sizeof(*wifi_conf)); + if (wifi_conf == NULL) { + nerr("ERROR: zalloc failed\n"); + return; + } + + wifi_conf->conf = *conf; + + work_queue(USRWORK, &wifi_conf->work, wifi_connect_worker, wifi_conf, conf->conn_delay); +} + +#endif diff --git a/music_player/wifi.h b/music_player/wifi.h new file mode 100644 index 0000000000000000000000000000000000000000..1441eeeddfbf389f47aa2955355aa2024e291f1c --- /dev/null +++ b/music_player/wifi.h @@ -0,0 +1,42 @@ +/** + * @file wifi.h + * Provide wifi connection and network configuration functions + */ + +/********************* + * INCLUDES + *********************/ +#include <nuttx/config.h> + +#if !defined(CONFIG_NETUTILS_DHCPC) || !defined(CONFIG_WIRELESS_WAPI) || !defined(CONFIG_NETUTILS_NTPCLIENT) || (CONFIG_SCHED_LPNTHREADS < 2) +#warning "If you want enable WIFI, please make sure DHCPC, WAPI, IEEE80211, NTP to be enabled \ + and number of lpthreads need greater than or equal to 2." +#define WIFI_ENABLED 0 +#else +#define WIFI_ENABLED 1 +#endif + +#if WIFI_ENABLED + +#include <nuttx/wqueue.h> + +#include "wireless/wapi.h" + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + char ssid[128]; + char pswd[128]; + enum wpa_ver_e ver_flag; + clock_t conn_delay; +} wifi_conf_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +void wifi_connect(FAR const wifi_conf_t* conf); + +#endif /* WIFI_ENABLED */ diff --git a/relation_calculator/Kconfig b/relation_calculator/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..3e069ad730fc680f6a9a84cdb5a6228a9099a568 --- /dev/null +++ b/relation_calculator/Kconfig @@ -0,0 +1,8 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config LVX_USE_DEMO_RELATIVES_CALCULATOR + bool "Relatives Calculator" + default n diff --git a/relation_calculator/Make.defs b/relation_calculator/Make.defs new file mode 100644 index 0000000000000000000000000000000000000000..9a6d721d371b3bd92951c72f9c12aef1070e7a25 --- /dev/null +++ b/relation_calculator/Make.defs @@ -0,0 +1,18 @@ +# +# Copyright (C) 2023 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +ifneq ($(CONFIG_LVX_USE_DEMO_RELATIVES_CALCULATOR),) +CONFIGURED_APPS += $(APPDIR)/packages/demos/relation_calculator +endif diff --git a/relation_calculator/Makefile b/relation_calculator/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..8c0e85df9a8fc8e6f70197c9f07ae27dd6840c64 --- /dev/null +++ b/relation_calculator/Makefile @@ -0,0 +1,35 @@ +# +# Copyright (C) 2022 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +include $(APPDIR)/Make.defs + +ifeq ($(CONFIG_LVX_USE_DEMO_RELATIVES_CALCULATOR), y) +PROGNAME = rel_cal +PRIORITY = 100 +STACKSIZE = 131072 +MODULE = $(CONFIG_LVX_USE_DEMO_RELATIVES_CALCULATOR) + +MAINSRC = relation_cal_main.c +CSRCS += relation_cal.c +ifeq ($(CONFIG_ESP32S3_BOX_LCD), y) +CSRCS += ./fonts/lv_font_siyuan_10.c +else +CSRCS += ./fonts/lv_font_siyuan_16.c +endif + +include $(APPDIR)/Application.mk + +endif \ No newline at end of file diff --git a/relation_calculator/Readme.md b/relation_calculator/Readme.md new file mode 100644 index 0000000000000000000000000000000000000000..4d5379f0ff915c016c814669c862a8c5daa2b3f8 --- /dev/null +++ b/relation_calculator/Readme.md @@ -0,0 +1,120 @@ +# Relative Calculator + +\[ English | [简体中文](Readme_zh-cn.md) \] + +## Running Effect + +![alt text](img/show.gif) + +## Usage Instructions + +### Running on Emulator + +#### Project Configuration + +1. Switch to the root directory of the openvela repository and execute the following command to configure the relative calculator. + + > The emulator configuration file (defconfig) is located in the `vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap/` directory. Use `build.sh` to configure and compile the development board code. + + ```Bash + ./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap menuconfig + ``` + +2. Press the `/` key to search and modify the following configuration one by one: + + ```Bash + LVX_USE_DEMO_RELATIVES_CALCULATOR=y + ``` + +#### Compiling the Project +```Bash +# Clean build artifacts +./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap distclean -j$(nproc) + +# Start building +./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap -j$(nproc) +``` + + + +#### Starting the Calculator +In the emulator's terminal environment `openvela-ap>`, enter the following command: + +```Bash +rel_cal & +``` + +### Running on ESP32S3-box +#### Project Configuration + +1. Switch to the root directory of the openvela repository and execute the following command to configure the relative calculator. + + > The emulator configuration file (defconfig) is located in the `nuttx/boards/xtensa/esp32s3/esp32s3-box/configs/lvgl-3` directory. Use `build.sh` to configure and compile the development board code. + + ```Bash + ./build.sh nuttx/boards/xtensa/esp32s3/esp32s3-box/configs/lvgl-3 menuconfig + ``` + +2. Press the `/` key to search and modify the following configuration one by one: + + ```Bash + LVX_USE_DEMO_RELATIVES_CALCULATOR=y + ``` + +#### Compiling the Project + +```Bash +# Clean build artifacts +./build.sh nuttx/boards/xtensa/esp32s3/esp32s3-box/configs/lvgl-3 distclean -j$(nproc) + +# Start building +./build.sh nuttx/boards/xtensa/esp32s3/esp32s3-box/configs/lvgl-3 -j$(nproc) +``` + + +#### Flashing Resources +Switch to the root directory of the nuttx repository and start flashing resources: + +```Bash +make -j20 flash ESPTOOL_PORT=/dev/ttyACM0 ESPTOOL_BINDIR=./ +``` + +#### Starting the Serial Terminal + +```Bash +sudo minicom -D /dev/ttyACM0 -b 115200 +``` + +#### Starting the Calculator +In the emulator's terminal environment `openvela-ap>`, enter the following command: + +```Bash +rel_cal & +``` + +## Adding Relationships +If you want to add new relationships, you need to modify the `/demos/relation_calculator/relation_cal.c` file according to the following steps. + +### Adding Relationship States +To add a state, simply add a new state to the `static const relation_transformation_t transitions[] = {...}` array, following the format defined by `relation_transformation_t`. The definition of `relation_transformation_t` is as follows: + +```C +typedef struct relation_transformation_s +{ + relation_type_t from; + relation_type_t to; + relation_type_t result; +} relation_transformation_t; +``` + +### Adding Supported Relationships +1. Add the new relationship type to `typedef enum relation_type_e`. +2. Add the new relationship name to `static const char *relation_names[]`. + +**Note:** Relationship names need to correspond one-to-one with relationship types. + +## Implementation Description +The relative calculator is implemented using a state transition approach. + +## Current Shortcomings +Both the family tree and state transition approaches inevitably require a large amount of code to build the relationships. Currently, no better implementation method has been thought of. We hope everyone can share better ideas for discussion. \ No newline at end of file diff --git a/relation_calculator/Readme_zh-cn.md b/relation_calculator/Readme_zh-cn.md new file mode 100644 index 0000000000000000000000000000000000000000..441042ae9732241f94123e121f72e2588d3ed1fe --- /dev/null +++ b/relation_calculator/Readme_zh-cn.md @@ -0,0 +1,120 @@ +# 亲戚计算器 + +\[ [English](Readme.md) | 简体中文 \] + +## 运行效果 + +![alt text](img/show.gif) + +## 使用说明 + +### 模拟器运行 + +#### 配置项目 + +1. 切换到 openvela 仓库的根目录,执行如下命令来配置亲戚计算器。 + + > 模拟器配置文件(defconfig)在 `vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap/` 目录下,使用 `build.sh` 配置和编译开发板的代码。 + + ```Bash + ./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap menuconfig + ``` + +2. 按下 `/` 键逐个搜索修改如下配置: + + ```Bash + LVX_USE_DEMO_RELATIVES_CALCULATOR=y + ``` + +#### 编译项目 +```Bash +# 清理构建产物 +./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap distclean -j$(nproc) + +# 开始构建 +./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap -j$(nproc) +``` + +#### 启动计算器 +在模拟器的终端环境 `openvela-ap>` 中输入如下命令: + +```Bash +rel_cal & +``` + +### ESP32S3-box 运行 +#### 配置项目 + +1. 切换到 openvela 仓库的根目录,执行如下命令来配置亲戚计算器。 + + > 模拟器配置文件(defconfig)在 `nuttx/boards/xtensa/esp32s3/esp32s3-box/configs/lvgl-3` 目录下,使用 `build.sh` 配置和编译开发板的代码。 + + ```Bash + ./build.sh nuttx/boards/xtensa/esp32s3/esp32s3-box/configs/lvgl-3 menuconfig + ``` + +2. 按下 `/` 键逐个搜索修改如下配置: + + ```Bash + LVX_USE_DEMO_RELATIVES_CALCULATOR=y + ``` + +#### 编译项目 + +```Bash +# 清理构建产物 +./build.sh nuttx/boards/xtensa/esp32s3/esp32s3-box/configs/lvgl-3 distclean -j$(nproc) + +# 开始构建 +./build.sh nuttx/boards/xtensa/esp32s3/esp32s3-box/configs/lvgl-3 -j$(nproc) +``` + +#### 烧录资源 +切换到 nuttx 仓库的根目录,开始烧录资源: + +```Bash +make -j20 flash ESPTOOL_PORT=/dev/ttyACM0 ESPTOOL_BINDIR=./ +``` + +#### 启动串口终端 + +```Bash +sudo minicom -D /dev/ttyACM0 -b 115200 +``` + +#### 启动计算器 +在模拟器的终端环境 `openvela-ap>` 中输入如下命令: + +```Bash +rel_cal & +``` + +## 增加关系 +如果想要增加新的关系,需要在 `/demos/relation_calculator/relation_cal.c` 文件中根据以下步骤进行修改。 + +### 增加关系状态 +添加状态只需要在 `static const relation_transformation_t transitions[] = {...}` 数组中添加新的状态,格式需要按照 `relation_transformation_t` 的定义来填写。 +`relation_transformation_t` 的定义如下: + +```C +typedef struct relation_transformation_s +{ + relation_type_t from; + relation_type_t to; + relation_type_t result; +} relation_transformation_t; +``` + +### 增加支持关系 +1. 在 `typedef enum relation_type_e` 中添加新的关系类型。 +2. 在 `static const char *relation_names[]` 中添加新的关系名称。 + +**注意:** 关系名称需要和关系类型一一对应。 + +## 实现说明 +采用了状态转移的方式来实现这个亲戚计算器。 + + +## 目前缺点 +家族树和状态转移的方式始终绕不开需要使用大量的代码来构建这个关系,目前想不到更好的实现方式了。希望大家有更好的想法可以一起讨论。 + diff --git a/relation_calculator/fonts/lv_font_siyuan_10.c b/relation_calculator/fonts/lv_font_siyuan_10.c new file mode 100644 index 0000000000000000000000000000000000000000..c7a589000cfa01575ec394444e516e05d46fe685 --- /dev/null +++ b/relation_calculator/fonts/lv_font_siyuan_10.c @@ -0,0 +1,1027 @@ +/******************************************************************************* + * Size: 10 px + * Bpp: 1 + * Opts: --bpp 1 --size 10 --no-compress --font SourceHanSansCN-Bold.ttf --symbols 父、亲、夫、丈、儿、子、哥、姐、母、妻、女、弟、妹、自、己、祖、伯、姑、堂、曾、外、舅、姨、侄、媳、婿、孙、姥、公、奶、爷、甥、嫡、庶、继、养、岳、妯、娌、玄、叔、清除、计算、<-、\n、空格、请、输、入、选、择、确、定、返、回、主、菜、单、计、算、结、果、关、系、帮、助、说、明、界、面、查、询、显、示、祖母、伯父、姑母、堂兄弟、堂姐妹、曾祖父、曾祖母、外祖父、外祖母、外曾祖父、外曾祖母、表侄、表侄女、再从兄弟、再从姐妹、叔祖父、姑祖母、舅父、姨母、侄子、侄女、儿媳、女婿、孙子、孙女、曾孙子、曾孙女、堂侄 / 表侄、堂侄女 / 表侄女、孙侄、孙侄女、未知、0、1、2、3、4、5、6、7、8、9、+、-、×、÷、=、?、!、,、。、:、;、《》、[]、{}、@、#、←、→、↑、↓、√、×、这、是、一、个、亲、属、关、系、计、算、器、。、未、支、持、关、系、:、“、未、知、”、。的 --range 32-127 --format lvgl -o lv_font_siyuan_10.c + ******************************************************************************/ + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +#ifndef LV_FONT_SIYUAN_10 +#define LV_FONT_SIYUAN_10 1 +#endif + +#if LV_FONT_SIYUAN_10 + +/*----------------- + * BITMAPS + *----------------*/ + +/*Store the image of the glyphs*/ +static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { + /* U+0020 " " */ + 0x0, + + /* U+0021 "!" */ + 0xaa, 0x8f, + + /* U+0022 "\"" */ + 0xff, 0xf0, + + /* U+0023 "#" */ + 0x52, 0xbe, 0xa5, 0x7e, 0x52, + + /* U+0024 "$" */ + 0x23, 0xb1, 0x8f, 0x1c, 0x73, 0xf1, 0x0, + + /* U+0025 "%" */ + 0x64, 0x4a, 0x25, 0x13, 0x66, 0xc8, 0xa4, 0x52, + 0x26, + + /* U+0026 "&" */ + 0x38, 0x91, 0x61, 0x87, 0x3b, 0xf3, 0x3d, + + /* U+0027 "'" */ + 0xfc, + + /* U+0028 "(" */ + 0x5a, 0xaa, 0x94, + + /* U+0029 ")" */ + 0xa5, 0x55, 0x68, + + /* U+002A "*" */ + 0x23, 0x88, 0xa0, + + /* U+002B "+" */ + 0x21, 0x3e, 0x42, 0x0, + + /* U+002C "," */ + 0xf6, + + /* U+002D "-" */ + 0xc0, + + /* U+002E "." */ + 0xf0, + + /* U+002F "/" */ + 0x22, 0x22, 0x24, 0x44, 0x48, 0x80, + + /* U+0030 "0" */ + 0x76, 0xf7, 0xbd, 0xef, 0x6e, + + /* U+0031 "1" */ + 0x6e, 0x66, 0x66, 0x6f, + + /* U+0032 "2" */ + 0x74, 0xc6, 0x33, 0x19, 0x9f, + + /* U+0033 "3" */ + 0x72, 0x42, 0x60, 0x85, 0x2e, + + /* U+0034 "4" */ + 0x39, 0xde, 0xbd, 0xfc, 0x63, + + /* U+0035 "5" */ + 0x79, 0x4, 0x1e, 0xc, 0x30, 0xde, + + /* U+0036 "6" */ + 0x76, 0x31, 0xed, 0xef, 0x6e, + + /* U+0037 "7" */ + 0xf8, 0x8c, 0x66, 0x31, 0x8c, + + /* U+0038 "8" */ + 0x73, 0x2c, 0x9e, 0x5b, 0x3c, 0xde, + + /* U+0039 "9" */ + 0x76, 0xf7, 0xb7, 0x8c, 0x6e, + + /* U+003A ":" */ + 0xf0, 0xf0, + + /* U+003B ";" */ + 0xf0, 0xf6, + + /* U+003C "<" */ + 0x1, 0xf9, 0xc3, 0x80, + + /* U+003D "=" */ + 0xf8, 0x1, 0xf0, + + /* U+003E ">" */ + 0x7, 0xe, 0x7e, 0x0, + + /* U+003F "?" */ + 0xe3, 0x36, 0x40, 0x66, + + /* U+0040 "@" */ + 0x1e, 0x30, 0x90, 0x33, 0x9a, 0x4d, 0x6e, 0xfd, + 0x0, 0x40, 0x1e, 0x0, + + /* U+0041 "A" */ + 0x30, 0xe3, 0x9a, 0x49, 0xfc, 0xf1, + + /* U+0042 "B" */ + 0xf3, 0x6d, 0xbc, 0xcf, 0x3c, 0xfe, + + /* U+0043 "C" */ + 0x3d, 0x9c, 0x30, 0xc3, 0x6, 0x4f, + + /* U+0044 "D" */ + 0xf3, 0x6c, 0xf3, 0xcf, 0x3d, 0xbc, + + /* U+0045 "E" */ + 0xfe, 0x31, 0xfc, 0x63, 0x1f, + + /* U+0046 "F" */ + 0xfe, 0x31, 0x8f, 0xe3, 0x18, + + /* U+0047 "G" */ + 0x39, 0xac, 0x30, 0xdf, 0x36, 0xcf, + + /* U+0048 "H" */ + 0xcf, 0x3c, 0xff, 0xcf, 0x3c, 0xf3, + + /* U+0049 "I" */ + 0xff, 0xff, + + /* U+004A "J" */ + 0x18, 0xc6, 0x31, 0x8e, 0x7e, + + /* U+004B "K" */ + 0xcd, 0xb3, 0x47, 0x8f, 0x99, 0x33, 0x62, + + /* U+004C "L" */ + 0xc6, 0x31, 0x8c, 0x63, 0x1f, + + /* U+004D "M" */ + 0xc7, 0xdf, 0xbf, 0x7e, 0xfa, 0xf5, 0xe3, + + /* U+004E "N" */ + 0xcf, 0xbe, 0xfb, 0xdf, 0x7d, 0xf3, + + /* U+004F "O" */ + 0x38, 0xdb, 0x1e, 0x3c, 0x78, 0xdb, 0x1c, + + /* U+0050 "P" */ + 0xfb, 0x3c, 0xf3, 0xfb, 0xc, 0x30, + + /* U+0051 "Q" */ + 0x38, 0xdb, 0x1e, 0x3c, 0x78, 0xdb, 0x1e, 0x18, + 0x1c, + + /* U+0052 "R" */ + 0xfb, 0x3c, 0xf3, 0xfb, 0x6c, 0xb3, + + /* U+0053 "S" */ + 0x76, 0xb1, 0xe7, 0x8e, 0x7e, + + /* U+0054 "T" */ + 0xfc, 0xc3, 0xc, 0x30, 0xc3, 0xc, + + /* U+0055 "U" */ + 0xcf, 0x3c, 0xf3, 0xcf, 0x3c, 0xde, + + /* U+0056 "V" */ + 0xc7, 0x34, 0xd2, 0x69, 0xe3, 0xc, + + /* U+0057 "W" */ + 0xc9, 0xe6, 0xd7, 0x6a, 0xa5, 0x52, 0xa9, 0xdc, + 0x66, + + /* U+0058 "X" */ + 0x4d, 0xa3, 0x8c, 0x31, 0xe4, 0xb3, + + /* U+0059 "Y" */ + 0xcf, 0x34, 0x9e, 0x30, 0xc3, 0xc, + + /* U+005A "Z" */ + 0xf8, 0xcc, 0x46, 0x23, 0x1f, + + /* U+005B "[" */ + 0xea, 0xaa, 0xb0, + + /* U+005C "\\" */ + 0x84, 0x44, 0x42, 0x22, 0x21, 0x10, + + /* U+005D "]" */ + 0xd5, 0x55, 0x70, + + /* U+005E "^" */ + 0x66, 0x69, 0x90, + + /* U+005F "_" */ + 0xfc, + + /* U+0060 "`" */ + 0x19, 0x80, + + /* U+0061 "a" */ + 0x70, 0xdf, 0xbd, 0xfc, + + /* U+0062 "b" */ + 0xc6, 0x3d, 0xbd, 0xef, 0x7e, + + /* U+0063 "c" */ + 0x7e, 0x31, 0x8c, 0xbc, + + /* U+0064 "d" */ + 0x18, 0xdf, 0xbd, 0xef, 0x6f, + + /* U+0065 "e" */ + 0x76, 0x7f, 0x8c, 0x3c, + + /* U+0066 "f" */ + 0xfb, 0xed, 0xb6, + + /* U+0067 "g" */ + 0x7f, 0x6d, 0x9c, 0xc1, 0xf8, 0xfe, + + /* U+0068 "h" */ + 0xc6, 0x3f, 0xbd, 0xef, 0x7b, + + /* U+0069 "i" */ + 0xcf, 0xff, + + /* U+006A "j" */ + 0x61, 0xb6, 0xdb, 0x78, + + /* U+006B "k" */ + 0xc3, 0xc, 0xb4, 0xf3, 0xcd, 0xb2, + + /* U+006C "l" */ + 0xdb, 0x6d, 0xb7, + + /* U+006D "m" */ + 0xff, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, + + /* U+006E "n" */ + 0xfe, 0xf7, 0xbd, 0xec, + + /* U+006F "o" */ + 0x7b, 0x3c, 0xf3, 0xcd, 0xe0, + + /* U+0070 "p" */ + 0xf6, 0xf7, 0xbd, 0xfb, 0x18, + + /* U+0071 "q" */ + 0x7e, 0xf7, 0xbd, 0xbc, 0x63, + + /* U+0072 "r" */ + 0xfc, 0xcc, 0xcc, + + /* U+0073 "s" */ + 0x78, 0xe7, 0x9e, + + /* U+0074 "t" */ + 0x66, 0xf6, 0x66, 0x63, + + /* U+0075 "u" */ + 0xde, 0xf7, 0xbd, 0xfc, + + /* U+0076 "v" */ + 0xc9, 0x24, 0x9e, 0x30, 0xc0, + + /* U+0077 "w" */ + 0xc9, 0x59, 0x5f, 0x57, 0x76, 0x36, + + /* U+0078 "x" */ + 0x4b, 0xcc, 0x65, 0xe4, + + /* U+0079 "y" */ + 0xc9, 0x25, 0x9c, 0x30, 0xc2, 0x18, + + /* U+007A "z" */ + 0xf3, 0x64, 0xcf, + + /* U+007B "{" */ + 0x69, 0x25, 0x12, 0x4c, + + /* U+007C "|" */ + 0xff, 0xf0, + + /* U+007D "}" */ + 0xc9, 0x24, 0x52, 0x58, + + /* U+007E "~" */ + 0x60, 0x60, + + /* U+00D7 "×" */ + 0x85, 0x23, 0xc, 0x4a, 0x10, + + /* U+00F7 "÷" */ + 0x18, 0x30, 0x7, 0xf0, 0x3, 0x6, 0x0, + + /* U+201C "“" */ + 0x5a, 0xf0, + + /* U+201D "”" */ + 0xf5, 0xa0, + + /* U+2190 "←" */ + 0x10, 0x8, 0xf, 0xfc, 0x80, 0x10, 0x0, + + /* U+2191 "↑" */ + 0x23, 0xbe, 0x42, 0x10, 0x84, 0x20, + + /* U+2192 "→" */ + 0x2, 0x0, 0x4f, 0xfc, 0x4, 0x2, 0x0, + + /* U+2193 "↓" */ + 0x21, 0x8, 0x42, 0x13, 0xee, 0x20, + + /* U+221A "√" */ + 0x7, 0xc1, 0x0, 0x80, 0x20, 0x8, 0x4, 0x5, + 0x1, 0x40, 0x20, 0x8, 0x0, + + /* U+3001 "、" */ + 0xc6, 0x20, + + /* U+3002 "。" */ + 0x69, 0x96, + + /* U+300A "《" */ + 0x1, 0x94, 0xaa, 0x51, 0x8a, 0x31, 0x40, + + /* U+300B "》" */ + 0x3, 0x14, 0xa2, 0x94, 0xca, 0x65, 0x0, + + /* U+4E00 "一" */ + 0xff, 0x80, + + /* U+4E08 "丈" */ + 0x4, 0x7f, 0xd1, 0xc, 0x82, 0xc0, 0xc0, 0xf1, + 0xcf, + + /* U+4E2A "个" */ + 0x8, 0xe, 0xd, 0x9d, 0x30, 0x80, 0x40, 0x20, + 0x10, 0x8, 0x0, + + /* U+4E3B "主" */ + 0x18, 0x6, 0x3f, 0xe1, 0x0, 0x83, 0xf8, 0x20, + 0x10, 0xff, 0x80, + + /* U+4EB2 "亲" */ + 0x8, 0x3f, 0xc8, 0x9f, 0xf0, 0x87, 0xfc, 0xa8, + 0x92, 0x19, 0x0, + + /* U+4ECE "从" */ + 0x22, 0x11, 0x8, 0x84, 0x43, 0x63, 0xf9, 0x14, + 0x9b, 0xd8, 0x80, + + /* U+4F2F "伯" */ + 0x24, 0x64, 0x5f, 0xd1, 0xd1, 0x5f, 0x51, 0x51, + 0x5f, 0x51, + + /* U+4F84 "侄" */ + 0x7f, 0xa4, 0xb6, 0x5f, 0xf4, 0x43, 0xfd, 0x10, + 0xff, 0x40, 0x0, + + /* U+513F "儿" */ + 0x22, 0x11, 0x8, 0x84, 0x42, 0x21, 0x11, 0x8a, + 0x85, 0x83, 0x80, + + /* U+5144 "兄" */ + 0x7f, 0x20, 0x90, 0x4f, 0xe3, 0x41, 0x20, 0x92, + 0xc9, 0xc7, 0x80, + + /* U+5165 "入" */ + 0x10, 0x2, 0x0, 0xc0, 0x38, 0x1a, 0x6, 0xc3, + 0x19, 0x83, 0x40, 0x0, + + /* U+516C "公" */ + 0x12, 0xc, 0xc6, 0x19, 0x22, 0x18, 0x4, 0xc2, + 0x11, 0xfe, 0x0, 0x80, + + /* U+5173 "关" */ + 0x63, 0x19, 0x1f, 0xc1, 0xf, 0xf8, 0xe0, 0xd9, + 0xc7, 0x0, 0x80, + + /* U+517B "养" */ + 0x22, 0x7f, 0xdf, 0xdf, 0xf6, 0x23, 0x1b, 0xde, + 0x4c, 0x46, 0x0, + + /* U+518D "再" */ + 0xff, 0x84, 0x1f, 0xc9, 0x27, 0xf2, 0x4b, 0xfe, + 0x82, 0x47, 0x0, + + /* U+52A9 "助" */ + 0x72, 0x29, 0x1d, 0xea, 0x57, 0x2a, 0x95, 0xf3, + 0xd9, 0xb, 0x80, + + /* U+5355 "单" */ + 0x22, 0x3f, 0x92, 0x4f, 0xe4, 0x91, 0xf3, 0xfe, + 0x10, 0x8, 0x0, + + /* U+53D4 "叔" */ + 0x27, 0x9f, 0xc9, 0x3f, 0xf2, 0x37, 0x5a, 0xad, + 0x4f, 0x64, 0x80, + + /* U+54E5 "哥" */ + 0xff, 0xbe, 0x91, 0x4f, 0xaf, 0xfb, 0xe9, 0x14, + 0xfa, 0x3, 0x0, + + /* U+5668 "器" */ + 0x77, 0x2a, 0x9d, 0xc3, 0x4f, 0xfb, 0x1b, 0xde, + 0xaa, 0x77, 0x0, + + /* U+56DE "回" */ + 0xff, 0x81, 0xbd, 0xa5, 0xa5, 0xbd, 0x81, 0xff, + 0x81, + + /* U+5802 "堂" */ + 0x49, 0x7f, 0xe0, 0x37, 0xd2, 0x20, 0xe1, 0xfc, + 0x10, 0xff, 0x80, + + /* U+5916 "外" */ + 0x22, 0xf, 0x84, 0xb1, 0xac, 0x3a, 0x86, 0x83, + 0x21, 0x88, 0x42, 0x0, + + /* U+592B "夫" */ + 0x8, 0x3f, 0x82, 0x1, 0xf, 0xf8, 0xe0, 0xd0, + 0xc6, 0xc1, 0x80, + + /* U+5973 "女" */ + 0x18, 0x8, 0x3f, 0xe4, 0x46, 0x63, 0xa0, 0x39, + 0xf7, 0xc1, 0x0, + + /* U+5976 "奶" */ + 0x4f, 0x7a, 0xbd, 0x5a, 0xff, 0x4b, 0x64, 0xe2, + 0xd1, 0xdb, 0x80, + + /* U+59AF "妯" */ + 0x42, 0x21, 0x3f, 0xf7, 0x5b, 0xfb, 0xd5, 0xeb, + 0xff, 0x98, 0x80, + + /* U+59B9 "妹" */ + 0x42, 0x77, 0xe8, 0x97, 0xfe, 0x73, 0x39, 0xbf, + 0xf5, 0x82, 0x0, + + /* U+59BB "妻" */ + 0xff, 0xbf, 0x9f, 0xdf, 0xf7, 0xf7, 0xfc, 0xc8, + 0x3c, 0xfb, 0x0, + + /* U+59D0 "姐" */ + 0x4f, 0x24, 0xba, 0x55, 0xea, 0x97, 0x79, 0x24, + 0xd2, 0x9f, 0x80, + + /* U+59D1 "姑" */ + 0x42, 0x21, 0x3f, 0xfa, 0x4f, 0xfb, 0x65, 0xf3, + 0xbf, 0x8c, 0x80, + + /* U+59E5 "姥" */ + 0x44, 0x2f, 0xb9, 0xd7, 0xfa, 0xc5, 0xf9, 0x70, + 0xd9, 0xa7, 0x0, + + /* U+59E8 "姨" */ + 0x5f, 0xb9, 0x6, 0x7a, 0xbe, 0xaf, 0xb9, 0x26, + 0xfb, 0xac, 0x90, 0x80, + + /* U+5A0C "娌" */ + 0x5f, 0xad, 0x7f, 0xf7, 0x5b, 0xfd, 0x11, 0x7e, + 0xc4, 0xbf, 0x80, + + /* U+5A7F "婿" */ + 0x5f, 0xa5, 0x7a, 0xf7, 0xfa, 0xff, 0x7d, 0xbf, + 0xd1, 0x89, 0x80, + + /* U+5AB3 "媳" */ + 0x44, 0x27, 0xbb, 0xd5, 0xea, 0xf7, 0x21, 0xec, + 0xf3, 0x9e, 0x0, + + /* U+5AE1 "嫡" */ + 0x42, 0x76, 0xeb, 0xf5, 0x5a, 0xaa, 0x7d, 0xff, + 0xd1, 0x89, 0x80, + + /* U+5B50 "子" */ + 0x7f, 0x3, 0x3, 0x1, 0xf, 0xf8, 0x40, 0x20, + 0x10, 0x38, 0x0, + + /* U+5B59 "孙" */ + 0xf2, 0x11, 0x13, 0xe9, 0x5e, 0xae, 0xd5, 0x8, + 0x84, 0x46, 0x0, + + /* U+5B9A "定" */ + 0x8, 0xff, 0x81, 0x7e, 0x68, 0x4f, 0x68, 0xf8, + 0x9f, + + /* U+5C5E "属" */ + 0x7f, 0x41, 0x7f, 0x5f, 0x5f, 0x5f, 0x7f, 0xff, + 0xb1, + + /* U+5CB3 "岳" */ + 0x0, 0x3f, 0x98, 0xf, 0xe6, 0x27, 0xfc, 0x20, + 0x92, 0x49, 0x3f, 0x80, + + /* U+5DF1 "己" */ + 0xfe, 0x2, 0x2, 0xfe, 0x82, 0x80, 0x81, 0x81, + 0xff, + + /* U+5E2E "帮" */ + 0xff, 0x92, 0xbf, 0x5f, 0x96, 0x53, 0xf9, 0x24, + 0x96, 0x8, 0x0, + + /* U+5EB6 "庶" */ + 0x4, 0x1f, 0xe4, 0x91, 0xfe, 0x49, 0x13, 0xc5, + 0x9, 0xda, 0x26, 0x80, + + /* U+5F1F "弟" */ + 0x22, 0x7f, 0x82, 0x4f, 0xe4, 0x83, 0xfc, 0xe7, + 0xd6, 0x8, 0x0, + + /* U+62E9 "择" */ + 0x5f, 0x77, 0x13, 0x8b, 0x6e, 0x42, 0x71, 0x7c, + 0x88, 0xc4, 0x0, + + /* U+6301 "持" */ + 0x42, 0x27, 0xf8, 0x8b, 0xf6, 0x17, 0xfd, 0x24, + 0x8a, 0xc3, 0x0, + + /* U+652F "支" */ + 0x8, 0x7f, 0xc2, 0xf, 0xe6, 0x31, 0xb0, 0x71, + 0xef, 0x0, 0x0, + + /* U+660E "明" */ + 0xef, 0xa9, 0xa9, 0xef, 0xa9, 0xef, 0x89, 0x11, + 0x13, + + /* U+662F "是" */ + 0x7f, 0x3f, 0x90, 0x4f, 0xef, 0xf9, 0x41, 0xbe, + 0xf0, 0x9f, 0x80, + + /* U+663E "显" */ + 0x7f, 0x20, 0x9f, 0xcf, 0xe5, 0x52, 0xa8, 0x51, + 0xff, + + /* U+66FE "曾" */ + 0x64, 0xff, 0xd5, 0x91, 0xff, 0x7e, 0x7e, 0x7e, + 0x42, + + /* U+672A "未" */ + 0x8, 0x3f, 0x82, 0x1, 0xf, 0xf8, 0xe0, 0xa9, + 0x93, 0x88, 0x0, + + /* U+679C "果" */ + 0x7f, 0x24, 0x9f, 0xc9, 0x23, 0xe7, 0xfc, 0xf9, + 0xd7, 0x88, 0x80, + + /* U+67E5 "查" */ + 0x8, 0x7f, 0xcf, 0x8d, 0x6f, 0xeb, 0xf1, 0x88, + 0x38, 0xff, 0x80, + + /* U+683C "格" */ + 0x4f, 0x7e, 0x99, 0x9f, 0xfd, 0x8e, 0x79, 0x24, + 0x9e, 0x49, 0x0, + + /* U+6BCD "母" */ + 0x3f, 0x34, 0x9b, 0x5f, 0xf5, 0x92, 0x49, 0xfe, + 0x4, 0x6, 0x0, + + /* U+6E05 "清" */ + 0x7f, 0xc3, 0xe4, 0x21, 0xff, 0xf, 0x8b, 0xe2, + 0xf9, 0x22, 0x49, 0x80, + + /* U+7236 "父" */ + 0x33, 0x8, 0x65, 0x28, 0x48, 0x1e, 0x3, 0x1, + 0xe1, 0xce, 0x40, 0x80, + + /* U+7237 "爷" */ + 0x12, 0x39, 0xd7, 0x83, 0xc6, 0x1b, 0xf8, 0x24, + 0x12, 0xb, 0x4, 0x0, + + /* U+7384 "玄" */ + 0x8, 0x7f, 0xc4, 0x4, 0x47, 0xc0, 0xd0, 0xc4, + 0xff, 0x0, 0x80, + + /* U+7525 "甥" */ + 0x6f, 0xbf, 0xea, 0xa5, 0xf7, 0x21, 0x7c, 0xca, + 0xc9, 0x9, 0x80, + + /* U+754C "界" */ + 0x7f, 0x24, 0x9f, 0xcf, 0xe3, 0x63, 0x1b, 0xde, + 0x6c, 0x66, 0x0, + + /* U+7684 "的" */ + 0x44, 0x32, 0x3d, 0xf3, 0x99, 0x8f, 0xb6, 0x4b, + 0x21, 0xf0, 0xc1, 0x80, + + /* U+77E5 "知" */ + 0x40, 0xff, 0xad, 0x2d, 0xfd, 0x6d, 0x7d, 0xdf, + 0x80, + + /* U+786E "确" */ + 0xf7, 0x24, 0x93, 0xff, 0x5d, 0xfe, 0xd5, 0x7e, + 0xf5, 0x9, 0x80, + + /* U+793A "示" */ + 0x7f, 0x0, 0x0, 0x1f, 0xf0, 0x83, 0x59, 0x25, + 0x13, 0x38, 0x0, + + /* U+7956 "祖" */ + 0x40, 0x27, 0xba, 0x5d, 0x26, 0xf7, 0x4b, 0xbc, + 0x92, 0x49, 0x2f, 0xc0, + + /* U+7A7A "空" */ + 0x8, 0x7f, 0xe0, 0x3e, 0xf7, 0xf0, 0x40, 0x21, + 0xff, + + /* U+7B97 "算" */ + 0x77, 0xd5, 0x9f, 0xcf, 0xe7, 0xf3, 0xfb, 0xfe, + 0xc4, 0x42, 0x0, + + /* U+7CFB "系" */ + 0x7, 0x3e, 0xd, 0x8f, 0x81, 0xa1, 0x99, 0xf6, + 0x54, 0x49, 0x4c, 0x0, + + /* U+7ED3 "结" */ + 0x22, 0x37, 0xf4, 0x9d, 0xf6, 0x7, 0xfc, 0x37, + 0xff, 0xd, 0x80, + + /* U+7EE7 "继" */ + 0x29, 0x13, 0x65, 0x9b, 0xbf, 0x6b, 0xbf, 0xd1, + 0x93, 0xbf, + + /* U+81EA "自" */ + 0x11, 0xfe, 0xf, 0xf8, 0x3f, 0xe0, 0xff, 0x82, + + /* U+8205 "舅" */ + 0x77, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0xfe, + 0x31, 0x63, 0x80, + + /* U+83DC "菜" */ + 0xff, 0x91, 0x3f, 0x89, 0x26, 0x97, 0xfc, 0xf9, + 0xd7, 0x88, 0x80, + + /* U+8868 "表" */ + 0xff, 0x84, 0x1f, 0xdf, 0xf3, 0x57, 0x3a, 0x8c, + 0xf3, 0x0, 0x0, + + /* U+8BA1 "计" */ + 0x44, 0x64, 0x4, 0xdf, 0x44, 0x44, 0x64, 0x64, + 0x44, + + /* U+8BE2 "询" */ + 0x8, 0x2f, 0xc4, 0x3b, 0xd5, 0x2a, 0xf5, 0xfa, + 0xa3, 0x3, 0x0, + + /* U+8BF4 "说" */ + 0x19, 0x25, 0x7, 0xda, 0x25, 0xf2, 0x51, 0xa8, + 0xf5, 0x33, 0x80, + + /* U+8BF7 "请" */ + 0xdf, 0xf, 0xb7, 0xfb, 0xf5, 0xf2, 0x89, 0xfc, + 0xfe, 0x13, 0x0, + + /* U+8F93 "输" */ + 0x46, 0x75, 0xa7, 0xf8, 0xf, 0xfa, 0xff, 0xfe, + 0xa9, 0x55, 0x80, + + /* U+8FD4 "返" */ + 0x1, 0x6f, 0x94, 0x3, 0xed, 0x12, 0xb9, 0x5c, + 0xfa, 0xa8, 0x4f, 0xc0, + + /* U+8FD9 "这" */ + 0x4, 0x62, 0x1f, 0xe2, 0x4d, 0xe2, 0x31, 0x6c, + 0xf2, 0xa0, 0x4f, 0xc0, + + /* U+9009 "选" */ + 0x94, 0x2f, 0x81, 0x1f, 0xe4, 0xa2, 0xd5, 0xcd, + 0xc0, 0x9f, 0x0, + + /* U+9664 "除" */ + 0x4, 0x72, 0x3a, 0xda, 0x3c, 0xf5, 0x22, 0xff, + 0xfa, 0x95, 0xc6, 0x0, + + /* U+9762 "面" */ + 0xff, 0x84, 0x3f, 0xfa, 0xbd, 0xde, 0xaf, 0x77, + 0xff, 0xc1, 0x80, + + /* U+FF01 "!" */ + 0xff, 0xcf, + + /* U+FF0C "," */ + 0xf6, + + /* U+FF1A ":" */ + 0xf0, 0x3c, + + /* U+FF1B ";" */ + 0xf0, 0x3d, 0x80, + + /* U+FF1F "?" */ + 0xf1, 0x12, 0x66, 0x60 +}; + + +/*--------------------- + * GLYPH DESCRIPTION + *--------------------*/ + +static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = { + {.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */, + {.bitmap_index = 0, .adv_w = 36, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1, .adv_w = 59, .box_w = 2, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 3, .adv_w = 92, .box_w = 4, .box_h = 3, .ofs_x = 1, .ofs_y = 5}, + {.bitmap_index = 5, .adv_w = 94, .box_w = 5, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 10, .adv_w = 94, .box_w = 5, .box_h = 10, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 17, .adv_w = 154, .box_w = 9, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 26, .adv_w = 118, .box_w = 7, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 33, .adv_w = 52, .box_w = 2, .box_h = 3, .ofs_x = 1, .ofs_y = 5}, + {.bitmap_index = 34, .adv_w = 60, .box_w = 2, .box_h = 11, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 37, .adv_w = 60, .box_w = 2, .box_h = 11, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 40, .adv_w = 81, .box_w = 5, .box_h = 4, .ofs_x = 0, .ofs_y = 4}, + {.bitmap_index = 43, .adv_w = 94, .box_w = 5, .box_h = 5, .ofs_x = 1, .ofs_y = 1}, + {.bitmap_index = 47, .adv_w = 52, .box_w = 2, .box_h = 4, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 48, .adv_w = 59, .box_w = 2, .box_h = 1, .ofs_x = 1, .ofs_y = 3}, + {.bitmap_index = 49, .adv_w = 52, .box_w = 2, .box_h = 2, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 50, .adv_w = 62, .box_w = 4, .box_h = 11, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 56, .adv_w = 94, .box_w = 5, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 61, .adv_w = 94, .box_w = 4, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 65, .adv_w = 94, .box_w = 5, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 70, .adv_w = 94, .box_w = 5, .box_h = 8, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 75, .adv_w = 94, .box_w = 5, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 80, .adv_w = 94, .box_w = 6, .box_h = 8, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 86, .adv_w = 94, .box_w = 5, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 91, .adv_w = 94, .box_w = 5, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 96, .adv_w = 94, .box_w = 6, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 102, .adv_w = 94, .box_w = 5, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 107, .adv_w = 52, .box_w = 2, .box_h = 6, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 109, .adv_w = 52, .box_w = 2, .box_h = 8, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 111, .adv_w = 94, .box_w = 5, .box_h = 6, .ofs_x = 1, .ofs_y = 1}, + {.bitmap_index = 115, .adv_w = 94, .box_w = 5, .box_h = 4, .ofs_x = 1, .ofs_y = 2}, + {.bitmap_index = 118, .adv_w = 94, .box_w = 5, .box_h = 6, .ofs_x = 1, .ofs_y = 1}, + {.bitmap_index = 122, .adv_w = 82, .box_w = 4, .box_h = 8, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 126, .adv_w = 161, .box_w = 9, .box_h = 10, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 138, .adv_w = 103, .box_w = 6, .box_h = 8, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 144, .adv_w = 109, .box_w = 6, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 150, .adv_w = 105, .box_w = 6, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 156, .adv_w = 114, .box_w = 6, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 162, .adv_w = 98, .box_w = 5, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 167, .adv_w = 94, .box_w = 5, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 172, .adv_w = 115, .box_w = 6, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 178, .adv_w = 121, .box_w = 6, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 184, .adv_w = 53, .box_w = 2, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 186, .adv_w = 91, .box_w = 5, .box_h = 8, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 191, .adv_w = 110, .box_w = 7, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 198, .adv_w = 92, .box_w = 5, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 203, .adv_w = 137, .box_w = 7, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 210, .adv_w = 120, .box_w = 6, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 216, .adv_w = 123, .box_w = 7, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 223, .adv_w = 107, .box_w = 6, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 229, .adv_w = 123, .box_w = 7, .box_h = 10, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 238, .adv_w = 109, .box_w = 6, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 244, .adv_w = 100, .box_w = 5, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 249, .adv_w = 100, .box_w = 6, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 255, .adv_w = 120, .box_w = 6, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 261, .adv_w = 99, .box_w = 6, .box_h = 8, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 267, .adv_w = 146, .box_w = 9, .box_h = 8, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 276, .adv_w = 100, .box_w = 6, .box_h = 8, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 282, .adv_w = 93, .box_w = 6, .box_h = 8, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 288, .adv_w = 98, .box_w = 5, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 293, .adv_w = 60, .box_w = 2, .box_h = 10, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 296, .adv_w = 62, .box_w = 4, .box_h = 11, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 302, .adv_w = 60, .box_w = 2, .box_h = 10, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 305, .adv_w = 94, .box_w = 4, .box_h = 5, .ofs_x = 1, .ofs_y = 3}, + {.bitmap_index = 308, .adv_w = 91, .box_w = 6, .box_h = 1, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 309, .adv_w = 100, .box_w = 3, .box_h = 3, .ofs_x = 1, .ofs_y = 7}, + {.bitmap_index = 311, .adv_w = 95, .box_w = 5, .box_h = 6, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 315, .adv_w = 103, .box_w = 5, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 320, .adv_w = 84, .box_w = 5, .box_h = 6, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 324, .adv_w = 103, .box_w = 5, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 329, .adv_w = 93, .box_w = 5, .box_h = 6, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 333, .adv_w = 60, .box_w = 3, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 336, .adv_w = 96, .box_w = 6, .box_h = 8, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 342, .adv_w = 102, .box_w = 5, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 347, .adv_w = 49, .box_w = 2, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 349, .adv_w = 49, .box_w = 3, .box_h = 10, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 353, .adv_w = 97, .box_w = 6, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 359, .adv_w = 50, .box_w = 3, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 362, .adv_w = 154, .box_w = 8, .box_h = 6, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 368, .adv_w = 103, .box_w = 5, .box_h = 6, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 372, .adv_w = 100, .box_w = 6, .box_h = 6, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 377, .adv_w = 103, .box_w = 5, .box_h = 8, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 382, .adv_w = 103, .box_w = 5, .box_h = 8, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 387, .adv_w = 70, .box_w = 4, .box_h = 6, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 390, .adv_w = 79, .box_w = 4, .box_h = 6, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 393, .adv_w = 67, .box_w = 4, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 397, .adv_w = 102, .box_w = 5, .box_h = 6, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 401, .adv_w = 92, .box_w = 6, .box_h = 6, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 406, .adv_w = 138, .box_w = 8, .box_h = 6, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 412, .adv_w = 90, .box_w = 5, .box_h = 6, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 416, .adv_w = 92, .box_w = 6, .box_h = 8, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 422, .adv_w = 82, .box_w = 4, .box_h = 6, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 425, .adv_w = 60, .box_w = 3, .box_h = 10, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 429, .adv_w = 47, .box_w = 1, .box_h = 12, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 431, .adv_w = 60, .box_w = 3, .box_h = 10, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 435, .adv_w = 94, .box_w = 6, .box_h = 2, .ofs_x = 0, .ofs_y = 3}, + {.bitmap_index = 437, .adv_w = 160, .box_w = 6, .box_h = 6, .ofs_x = 2, .ofs_y = 1}, + {.bitmap_index = 442, .adv_w = 160, .box_w = 7, .box_h = 7, .ofs_x = 1, .ofs_y = 1}, + {.bitmap_index = 449, .adv_w = 160, .box_w = 4, .box_h = 3, .ofs_x = 6, .ofs_y = 6}, + {.bitmap_index = 451, .adv_w = 160, .box_w = 4, .box_h = 3, .ofs_x = 1, .ofs_y = 6}, + {.bitmap_index = 453, .adv_w = 160, .box_w = 10, .box_h = 5, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 460, .adv_w = 160, .box_w = 5, .box_h = 9, .ofs_x = 3, .ofs_y = -1}, + {.bitmap_index = 466, .adv_w = 160, .box_w = 10, .box_h = 5, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 473, .adv_w = 160, .box_w = 5, .box_h = 9, .ofs_x = 3, .ofs_y = -1}, + {.bitmap_index = 479, .adv_w = 160, .box_w = 10, .box_h = 10, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 492, .adv_w = 160, .box_w = 4, .box_h = 3, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 494, .adv_w = 160, .box_w = 4, .box_h = 4, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 496, .adv_w = 160, .box_w = 5, .box_h = 10, .ofs_x = 5, .ofs_y = -1}, + {.bitmap_index = 503, .adv_w = 160, .box_w = 5, .box_h = 10, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 510, .adv_w = 160, .box_w = 9, .box_h = 1, .ofs_x = 0, .ofs_y = 3}, + {.bitmap_index = 512, .adv_w = 160, .box_w = 9, .box_h = 8, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 521, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 532, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 543, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 554, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 565, .adv_w = 160, .box_w = 8, .box_h = 10, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 575, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 586, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 597, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 608, .adv_w = 160, .box_w = 10, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 620, .adv_w = 160, .box_w = 10, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 632, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 643, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 654, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 665, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 676, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 687, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 698, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 709, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 720, .adv_w = 160, .box_w = 8, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 729, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 740, .adv_w = 160, .box_w = 10, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 752, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 763, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 774, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 785, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 796, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 807, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 818, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 829, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 840, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 851, .adv_w = 160, .box_w = 10, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 863, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 874, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 885, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 896, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 907, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 918, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 929, .adv_w = 160, .box_w = 8, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 938, .adv_w = 160, .box_w = 8, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 947, .adv_w = 160, .box_w = 9, .box_h = 10, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 959, .adv_w = 160, .box_w = 8, .box_h = 9, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 968, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 979, .adv_w = 160, .box_w = 10, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 991, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1002, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1013, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1024, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1035, .adv_w = 160, .box_w = 8, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1044, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1055, .adv_w = 160, .box_w = 9, .box_h = 8, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1064, .adv_w = 160, .box_w = 8, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1073, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1084, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1095, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1106, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1117, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1128, .adv_w = 160, .box_w = 10, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1140, .adv_w = 160, .box_w = 10, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1152, .adv_w = 160, .box_w = 9, .box_h = 10, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1164, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1175, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1186, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1197, .adv_w = 160, .box_w = 9, .box_h = 10, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1209, .adv_w = 160, .box_w = 8, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1218, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1229, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1240, .adv_w = 160, .box_w = 9, .box_h = 10, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1252, .adv_w = 160, .box_w = 9, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 1261, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1272, .adv_w = 160, .box_w = 9, .box_h = 10, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1284, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1295, .adv_w = 160, .box_w = 10, .box_h = 8, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1305, .adv_w = 160, .box_w = 7, .box_h = 9, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 1313, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1324, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1335, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1346, .adv_w = 160, .box_w = 8, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1355, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1366, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1377, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1388, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1399, .adv_w = 160, .box_w = 9, .box_h = 10, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1411, .adv_w = 160, .box_w = 9, .box_h = 10, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1423, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1434, .adv_w = 160, .box_w = 9, .box_h = 10, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1446, .adv_w = 160, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1457, .adv_w = 160, .box_w = 2, .box_h = 8, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 1459, .adv_w = 160, .box_w = 2, .box_h = 4, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 1460, .adv_w = 160, .box_w = 2, .box_h = 7, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 1462, .adv_w = 160, .box_w = 2, .box_h = 9, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 1465, .adv_w = 160, .box_w = 4, .box_h = 7, .ofs_x = 0, .ofs_y = 0} +}; + +/*--------------------- + * CHARACTER MAPPING + *--------------------*/ + +static const uint16_t unicode_list_1[] = { + 0x0, 0x20, 0x1f45, 0x1f46, 0x20b9, 0x20ba, 0x20bb, 0x20bc, + 0x2143, 0x2f2a, 0x2f2b, 0x2f33, 0x2f34, 0x4d29, 0x4d31, 0x4d53, + 0x4d64, 0x4ddb, 0x4df7, 0x4e58, 0x4ead, 0x5068, 0x506d, 0x508e, + 0x5095, 0x509c, 0x50a4, 0x50b6, 0x51d2, 0x527e, 0x52fd, 0x540e, + 0x5591, 0x5607, 0x572b, 0x583f, 0x5854, 0x589c, 0x589f, 0x58d8, + 0x58e2, 0x58e4, 0x58f9, 0x58fa, 0x590e, 0x5911, 0x5935, 0x59a8, + 0x59dc, 0x5a0a, 0x5a79, 0x5a82, 0x5ac3, 0x5b87, 0x5bdc, 0x5d1a, + 0x5d57, 0x5ddf, 0x5e48, 0x6212, 0x622a, 0x6458, 0x6537, 0x6558, + 0x6567, 0x6627, 0x6653, 0x66c5, 0x670e, 0x6765, 0x6af6, 0x6d2e, + 0x715f, 0x7160, 0x72ad, 0x744e, 0x7475, 0x75ad, 0x770e, 0x7797, + 0x7863, 0x787f, 0x79a3, 0x7ac0, 0x7c24, 0x7dfc, 0x7e10, 0x8113, + 0x812e, 0x8305, 0x8791, 0x8aca, 0x8b0b, 0x8b1d, 0x8b20, 0x8ebc, + 0x8efd, 0x8f02, 0x8f32, 0x958d, 0x968b, 0xfe2a, 0xfe35, 0xfe43, + 0xfe44, 0xfe48 +}; + +/*Collect the unicode lists and glyph_id offsets*/ +static const lv_font_fmt_txt_cmap_t cmaps[] = +{ + { + .range_start = 32, .range_length = 95, .glyph_id_start = 1, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 215, .range_length = 65097, .glyph_id_start = 96, + .unicode_list = unicode_list_1, .glyph_id_ofs_list = NULL, .list_length = 106, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY + } +}; + + + +/*-------------------- + * ALL CUSTOM DATA + *--------------------*/ + +#if LVGL_VERSION_MAJOR == 8 +/*Store all the custom data of the font*/ +static lv_font_fmt_txt_glyph_cache_t cache; +#endif + +#if LVGL_VERSION_MAJOR >= 8 +static const lv_font_fmt_txt_dsc_t font_dsc = { +#else +static lv_font_fmt_txt_dsc_t font_dsc = { +#endif + .glyph_bitmap = glyph_bitmap, + .glyph_dsc = glyph_dsc, + .cmaps = cmaps, + .kern_dsc = NULL, + .kern_scale = 0, + .cmap_num = 2, + .bpp = 1, + .kern_classes = 0, + .bitmap_format = 0, +#if LVGL_VERSION_MAJOR == 8 + .cache = &cache +#endif +}; + + + +/*----------------- + * PUBLIC FONT + *----------------*/ + +/*Initialize a public general font descriptor*/ +#if LVGL_VERSION_MAJOR >= 8 +const lv_font_t lv_font_siyuan_10 = { +#else +lv_font_t lv_font_siyuan_10 = { +#endif + .get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/ + .get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/ + .line_height = 13, /*The maximum line height required by the font*/ + .base_line = 3, /*Baseline measured from the bottom of the line*/ +#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0) + .subpx = LV_FONT_SUBPX_NONE, +#endif +#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8 + .underline_position = -1, + .underline_thickness = 1, +#endif + .dsc = &font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */ +#if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9 + .fallback = NULL, +#endif + .user_data = NULL, +}; + + + +#endif /*#if LV_FONT_SIYUAN_10*/ + diff --git a/relation_calculator/fonts/lv_font_siyuan_16.c b/relation_calculator/fonts/lv_font_siyuan_16.c new file mode 100644 index 0000000000000000000000000000000000000000..1e36956c0fa095177a64834f9eb7c90cc84d1d9e --- /dev/null +++ b/relation_calculator/fonts/lv_font_siyuan_16.c @@ -0,0 +1,1274 @@ +/******************************************************************************* + * Size: 16 px + * Bpp: 1 + * Opts: --bpp 1 --size 16 --no-compress --font SourceHanSansCN-Bold.ttf --symbols 父、亲、夫、丈、儿、子、哥、姐、母、妻、女、弟、妹、自、己、祖、伯、姑、堂、曾、外、舅、姨、侄、媳、婿、孙、姥、公、奶、爷、甥、嫡、庶、继、养、岳、妯、娌、玄、叔、清除、计算、<-、\n、空格、请、输、入、选、择、确、定、返、回、主、菜、单、计、算、结、果、关、系、帮、助、说、明、界、面、查、询、显、示、祖母、伯父、姑母、堂兄弟、堂姐妹、曾祖父、曾祖母、外祖父、外祖母、外曾祖父、外曾祖母、表侄、表侄女、再从兄弟、再从姐妹、叔祖父、姑祖母、舅父、姨母、侄子、侄女、儿媳、女婿、孙子、孙女、曾孙子、曾孙女、堂侄 / 表侄、堂侄女 / 表侄女、孙侄、孙侄女、未知、0、1、2、3、4、5、6、7、8、9、+、-、×、÷、=、?、!、,、。、:、;、《》、[]、{}、@、#、←、→、↑、↓、√、×、这、是、一、个、亲、属、关、系、计、算、器、。、未、支、持、关、系、:、“、未、知、”、。的 --range 32-127 --format lvgl -o lv_font_siyuan_16.c + ******************************************************************************/ + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +#ifndef LV_FONT_SIYUAN_16 +#define LV_FONT_SIYUAN_16 1 +#endif + +#if LV_FONT_SIYUAN_16 + +/*----------------- + * BITMAPS + *----------------*/ + +/*Store the image of the glyphs*/ +static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { + /* U+0020 " " */ + 0x0, + + /* U+0021 "!" */ + 0xdb, 0x6d, 0xb6, 0x1f, 0xf0, + + /* U+0022 "\"" */ + 0xcf, 0x3c, 0xf3, 0xcc, + + /* U+0023 "#" */ + 0x32, 0x22, 0x26, 0xff, 0xff, 0x66, 0x64, 0xff, + 0xff, 0x44, 0x4c, 0x4c, + + /* U+0024 "$" */ + 0x30, 0x61, 0xfe, 0x2c, 0x1c, 0x3c, 0x3e, 0x1e, + 0xc, 0x1e, 0x3f, 0xc6, 0xc, 0x0, + + /* U+0025 "%" */ + 0x78, 0x60, 0xf0, 0x83, 0x33, 0x6, 0x64, 0xc, + 0xdb, 0x99, 0xaf, 0x9e, 0xf1, 0xbd, 0x63, 0x6, + 0xc6, 0x9, 0x8c, 0x21, 0xf0, 0x41, 0xc0, + + /* U+0026 "&" */ + 0x3c, 0xf, 0xc1, 0x98, 0x33, 0x6, 0xc0, 0x71, + 0xde, 0x36, 0xe6, 0xcf, 0x98, 0xf3, 0xff, 0x9e, + 0x20, + + /* U+0027 "'" */ + 0xff, 0xc0, + + /* U+0028 "(" */ + 0x32, 0x66, 0xcc, 0xcc, 0xcc, 0xcc, 0x66, 0x63, + + /* U+0029 ")" */ + 0xc4, 0x66, 0x33, 0x33, 0x33, 0x33, 0x66, 0x4c, + + /* U+002A "*" */ + 0x30, 0xcf, 0xde, 0x79, 0x20, + + /* U+002B "+" */ + 0x18, 0x18, 0x18, 0xff, 0xff, 0x18, 0x18, 0x18, + + /* U+002C "," */ + 0x77, 0x37, 0xe0, + + /* U+002D "-" */ + 0xff, + + /* U+002E "." */ + 0xff, 0x80, + + /* U+002F "/" */ + 0xc, 0x20, 0x86, 0x18, 0x41, 0xc, 0x30, 0x86, + 0x18, 0x61, 0xc, 0x30, + + /* U+0030 "0" */ + 0x3c, 0x7e, 0x66, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, + 0xc3, 0x66, 0x7e, 0x3c, + + /* U+0031 "1" */ + 0x39, 0xf0, 0x60, 0xc1, 0x83, 0x6, 0xc, 0x18, + 0x33, 0xff, 0xf0, + + /* U+0032 "2" */ + 0x78, 0xfc, 0xc6, 0x6, 0x6, 0x6, 0xc, 0x1c, + 0x38, 0x70, 0xff, 0xff, + + /* U+0033 "3" */ + 0x78, 0xfe, 0x46, 0x6, 0xe, 0x3c, 0x3e, 0x7, + 0x3, 0xc7, 0xfe, 0x7c, + + /* U+0034 "4" */ + 0xe, 0xf, 0x7, 0x86, 0xc3, 0x63, 0x33, 0x99, + 0xff, 0xff, 0x83, 0x1, 0x80, 0xc0, + + /* U+0035 "5" */ + 0x7f, 0x7f, 0x60, 0x60, 0x7c, 0x7e, 0x7, 0x3, + 0x3, 0x47, 0xfe, 0x7c, + + /* U+0036 "6" */ + 0x1e, 0x7f, 0x62, 0xc0, 0xdc, 0xfe, 0xe3, 0xc3, + 0xc3, 0x67, 0x7e, 0x3c, + + /* U+0037 "7" */ + 0xff, 0xff, 0x6, 0xc, 0x1c, 0x18, 0x18, 0x30, + 0x30, 0x30, 0x30, 0x30, + + /* U+0038 "8" */ + 0x3c, 0xfd, 0x9b, 0x36, 0x67, 0x9f, 0x63, 0xc7, + 0x8f, 0xfb, 0xe0, + + /* U+0039 "9" */ + 0x3c, 0x7e, 0xe6, 0xc3, 0xc3, 0xc7, 0x7f, 0x3b, + 0x3, 0x46, 0xfe, 0x78, + + /* U+003A ":" */ + 0xff, 0x80, 0x3f, 0xe0, + + /* U+003B ";" */ + 0xff, 0x80, 0x7, 0xef, 0xe0, + + /* U+003C "<" */ + 0x1, 0x7, 0x3e, 0xf0, 0xe0, 0x7c, 0xf, 0x1, + + /* U+003D "=" */ + 0xff, 0xff, 0x0, 0x0, 0xff, 0xff, + + /* U+003E ">" */ + 0x80, 0xe0, 0x7c, 0xf, 0x7, 0x3e, 0xf0, 0x80, + + /* U+003F "?" */ + 0x3d, 0xfd, 0x18, 0x30, 0xc3, 0x86, 0x1c, 0x0, + 0x70, 0xe1, 0xc0, + + /* U+0040 "@" */ + 0x3, 0xe0, 0x1f, 0xf0, 0xf0, 0xf1, 0x80, 0x66, + 0x3e, 0x6c, 0xfc, 0xf1, 0x91, 0xe6, 0x23, 0xcc, + 0xc7, 0x99, 0x9b, 0x3d, 0xf7, 0x3b, 0xc6, 0x0, + 0xf, 0x0, 0xf, 0xf0, 0x7, 0xc0, + + /* U+0041 "A" */ + 0xe, 0x7, 0x81, 0xe0, 0x6c, 0x33, 0xc, 0xc3, + 0x39, 0xfe, 0x7f, 0x98, 0x7e, 0x1f, 0x3, + + /* U+0042 "B" */ + 0xfe, 0x7f, 0xb0, 0xd8, 0x6c, 0x77, 0xf3, 0xfd, + 0x87, 0xc1, 0xe1, 0xff, 0xdf, 0xc0, + + /* U+0043 "C" */ + 0x1e, 0x1f, 0xdc, 0x5c, 0xc, 0x6, 0x3, 0x1, + 0x80, 0xe0, 0x30, 0x8f, 0xe3, 0xc0, + + /* U+0044 "D" */ + 0xfc, 0x7f, 0x31, 0xd8, 0x7c, 0x1e, 0xf, 0x7, + 0x83, 0xc3, 0xe3, 0xbf, 0x9f, 0x80, + + /* U+0045 "E" */ + 0xff, 0xff, 0xc0, 0xc0, 0xc0, 0xfe, 0xfe, 0xc0, + 0xc0, 0xc0, 0xff, 0xff, + + /* U+0046 "F" */ + 0xff, 0xff, 0xc0, 0xc0, 0xc0, 0xfe, 0xfe, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, + + /* U+0047 "G" */ + 0x1f, 0x1f, 0xdc, 0x5c, 0xc, 0x6, 0x3f, 0x1f, + 0x83, 0xe1, 0xb8, 0xcf, 0xe3, 0xe0, + + /* U+0048 "H" */ + 0xc1, 0xe0, 0xf0, 0x78, 0x3c, 0x1f, 0xff, 0xff, + 0x83, 0xc1, 0xe0, 0xf0, 0x78, 0x30, + + /* U+0049 "I" */ + 0xff, 0xff, 0xff, + + /* U+004A "J" */ + 0x6, 0xc, 0x18, 0x30, 0x60, 0xc1, 0x83, 0x6, + 0x8f, 0xf3, 0xc0, + + /* U+004B "K" */ + 0xc3, 0x63, 0x33, 0x9b, 0x8d, 0x87, 0xc3, 0xf1, + 0xdc, 0xc6, 0x63, 0xb0, 0xd8, 0x70, + + /* U+004C "L" */ + 0xc1, 0x83, 0x6, 0xc, 0x18, 0x30, 0x60, 0xc1, + 0x83, 0xff, 0xf0, + + /* U+004D "M" */ + 0xe0, 0xfc, 0x1f, 0x83, 0xf8, 0xff, 0x1f, 0xb6, + 0xf6, 0xde, 0xdb, 0xce, 0x79, 0xcf, 0x11, 0xe0, + 0x30, + + /* U+004E "N" */ + 0xe1, 0xf0, 0xfc, 0x7e, 0x3d, 0x9e, 0xef, 0x37, + 0x9f, 0xc7, 0xe3, 0xf0, 0xf8, 0x30, + + /* U+004F "O" */ + 0x1e, 0x1f, 0xe6, 0x1b, 0x87, 0xc0, 0xf0, 0x3c, + 0xf, 0x3, 0xe1, 0xd8, 0x67, 0xf8, 0x78, + + /* U+0050 "P" */ + 0xfc, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe, 0xfc, + 0xc0, 0xc0, 0xc0, 0xc0, + + /* U+0051 "Q" */ + 0x1e, 0xf, 0xf1, 0x86, 0x70, 0xec, 0xd, 0x81, + 0xb0, 0x36, 0x6, 0xe1, 0xcc, 0x31, 0xfe, 0xf, + 0x0, 0xe0, 0xf, 0x80, 0xf0, + + /* U+0052 "R" */ + 0xfc, 0x7f, 0x31, 0xd8, 0x6c, 0x36, 0x3b, 0xf9, + 0xf8, 0xce, 0x63, 0x31, 0xd8, 0x60, + + /* U+0053 "S" */ + 0x1e, 0x1f, 0xd8, 0x4c, 0x7, 0x1, 0xf0, 0x3c, + 0x7, 0x1, 0xb0, 0xdf, 0xc7, 0xc0, + + /* U+0054 "T" */ + 0xff, 0xff, 0xc3, 0x1, 0x80, 0xc0, 0x60, 0x30, + 0x18, 0xc, 0x6, 0x3, 0x1, 0x80, + + /* U+0055 "U" */ + 0xc1, 0xe0, 0xf0, 0x78, 0x3c, 0x1e, 0xf, 0x7, + 0x83, 0xc1, 0xf1, 0xdf, 0xc7, 0xc0, + + /* U+0056 "V" */ + 0xe1, 0xf8, 0x76, 0x19, 0x86, 0x73, 0x8c, 0xc3, + 0x30, 0xcc, 0x3e, 0x7, 0x81, 0xe0, 0x78, + + /* U+0057 "W" */ + 0xe3, 0x8d, 0x8e, 0x36, 0x38, 0xd8, 0xe3, 0x66, + 0x9d, 0xdb, 0x63, 0x6d, 0x8d, 0x36, 0x3c, 0xd8, + 0xf1, 0x63, 0xc7, 0xf, 0x1c, + + /* U+0058 "X" */ + 0x61, 0x9c, 0xe3, 0x30, 0xec, 0x1e, 0x7, 0x81, + 0xe0, 0x78, 0x37, 0xc, 0xc6, 0x3b, 0x87, + + /* U+0059 "Y" */ + 0x61, 0x9c, 0x63, 0x30, 0xcc, 0x1e, 0x7, 0x81, + 0xe0, 0x30, 0xc, 0x3, 0x0, 0xc0, 0x30, + + /* U+005A "Z" */ + 0xff, 0xff, 0x6, 0xe, 0xc, 0x18, 0x38, 0x30, + 0x60, 0x60, 0xff, 0xff, + + /* U+005B "[" */ + 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xf0, + + /* U+005C "\\" */ + 0x41, 0x6, 0x18, 0x20, 0x83, 0xc, 0x10, 0x41, + 0x86, 0x8, 0x20, 0xc3, + + /* U+005D "]" */ + 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0xf0, + + /* U+005E "^" */ + 0x18, 0x70, 0xf3, 0x66, 0x48, 0xf1, 0x80, + + /* U+005F "_" */ + 0xff, 0xff, 0xc0, + + /* U+0060 "`" */ + 0xce, 0x70, + + /* U+0061 "a" */ + 0x3d, 0xfd, 0x18, 0x77, 0xf8, 0xf1, 0xff, 0x76, + + /* U+0062 "b" */ + 0xc0, 0xc0, 0xc0, 0xc0, 0xdc, 0xfe, 0xc7, 0xc3, + 0xc3, 0xc3, 0xc7, 0xfe, 0xdc, + + /* U+0063 "c" */ + 0x3c, 0xfb, 0x86, 0xc, 0x18, 0x38, 0x3f, 0x3c, + + /* U+0064 "d" */ + 0x3, 0x3, 0x3, 0x3, 0x3b, 0x7f, 0xe3, 0xc3, + 0xc3, 0xc3, 0xe3, 0x7f, 0x3b, + + /* U+0065 "e" */ + 0x3c, 0x7e, 0xe3, 0xff, 0xff, 0xe0, 0xe0, 0x7e, + 0x3e, + + /* U+0066 "f" */ + 0x3d, 0xf6, 0x18, 0xfb, 0xe6, 0x18, 0x61, 0x86, + 0x18, 0x60, + + /* U+0067 "g" */ + 0x7f, 0xff, 0xc6, 0xc6, 0x7e, 0x7c, 0xc0, 0xfe, + 0x7f, 0xc3, 0xfe, 0x7c, + + /* U+0068 "h" */ + 0xc0, 0xc0, 0xc0, 0xc0, 0xde, 0xff, 0xe3, 0xc3, + 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, + + /* U+0069 "i" */ + 0xf0, 0xff, 0xff, 0xc0, + + /* U+006A "j" */ + 0x33, 0x0, 0x33, 0x33, 0x33, 0x33, 0x33, 0xfe, + + /* U+006B "k" */ + 0xc0, 0xc0, 0xc0, 0xc0, 0xc6, 0xcc, 0xdc, 0xf8, + 0xf8, 0xfc, 0xcc, 0xc6, 0xc7, + + /* U+006C "l" */ + 0xdb, 0x6d, 0xb6, 0xdb, 0x76, + + /* U+006D "m" */ + 0xdc, 0xef, 0xff, 0xe7, 0x3c, 0x63, 0xc6, 0x3c, + 0x63, 0xc6, 0x3c, 0x63, 0xc6, 0x30, + + /* U+006E "n" */ + 0xde, 0xff, 0xe3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, + 0xc3, + + /* U+006F "o" */ + 0x3c, 0x7e, 0xe7, 0xc3, 0xc3, 0xc3, 0xe7, 0x7e, + 0x3c, + + /* U+0070 "p" */ + 0xdc, 0xfe, 0xc7, 0xc3, 0xc3, 0xc3, 0xc7, 0xfe, + 0xdc, 0xc0, 0xc0, 0xc0, + + /* U+0071 "q" */ + 0x3b, 0x7f, 0xe3, 0xc3, 0xc3, 0xc3, 0xe3, 0x7f, + 0x3b, 0x3, 0x3, 0x3, + + /* U+0072 "r" */ + 0xdf, 0xf9, 0x8c, 0x63, 0x18, 0xc0, + + /* U+0073 "s" */ + 0x7b, 0xfc, 0x38, 0x78, 0x38, 0xff, 0x78, + + /* U+0074 "t" */ + 0x61, 0x86, 0x3f, 0xfd, 0x86, 0x18, 0x61, 0x87, + 0xcf, + + /* U+0075 "u" */ + 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc7, 0xff, + 0x7b, + + /* U+0076 "v" */ + 0xe1, 0xb1, 0x98, 0xce, 0x63, 0x71, 0xb0, 0xd8, + 0x3c, 0x1c, 0x0, + + /* U+0077 "w" */ + 0x63, 0x1b, 0x18, 0xd9, 0xe6, 0xef, 0x73, 0x5b, + 0x1a, 0x58, 0xf3, 0xc7, 0x9e, 0x1c, 0xe0, + + /* U+0078 "x" */ + 0x63, 0x3b, 0x8d, 0x83, 0xc1, 0xc1, 0xf0, 0xd8, + 0xce, 0x63, 0x0, + + /* U+0079 "y" */ + 0xe1, 0xb1, 0x9c, 0xc6, 0x63, 0x60, 0xf0, 0x78, + 0x38, 0xc, 0xe, 0x1e, 0xe, 0x0, + + /* U+007A "z" */ + 0x7e, 0xfc, 0x30, 0xe3, 0x86, 0x1c, 0x7f, 0xfe, + + /* U+007B "{" */ + 0x19, 0x8c, 0x63, 0x1b, 0xde, 0x31, 0x8c, 0x63, + 0x1c, 0x60, + + /* U+007C "|" */ + 0xff, 0xff, 0xff, 0xff, 0xf0, + + /* U+007D "}" */ + 0xc3, 0x18, 0xc6, 0x31, 0xef, 0x63, 0x18, 0xc6, + 0x73, 0x0, + + /* U+007E "~" */ + 0x72, 0xfe, 0x9e, + + /* U+00D7 "×" */ + 0xc0, 0xf8, 0x77, 0x38, 0xfc, 0x1e, 0x7, 0x83, + 0xf1, 0xce, 0xe1, 0xf0, 0x30, + + /* U+00F7 "÷" */ + 0xe, 0x1, 0xc0, 0x0, 0x0, 0xf, 0xff, 0xff, + 0xc0, 0x0, 0x0, 0xe, 0x1, 0xc0, + + /* U+201C "“" */ + 0x1, 0x18, 0xb3, 0xcc, + + /* U+201D "”" */ + 0xcf, 0x34, 0x62, 0x0, + + /* U+2190 "←" */ + 0x18, 0x0, 0xe0, 0x7, 0x0, 0x3f, 0xff, 0x70, + 0x0, 0xe0, 0x1, 0x80, 0x0, + + /* U+2191 "↑" */ + 0x0, 0x18, 0x3c, 0x7e, 0xdb, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + + /* U+2192 "→" */ + 0x0, 0x60, 0x1, 0xc0, 0x3, 0xbf, 0xff, 0x0, + 0x38, 0x1, 0xc0, 0x6, 0x0, + + /* U+2193 "↓" */ + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0xdb, 0x7e, 0x3c, 0x18, 0x0, + + /* U+221A "√" */ + 0x0, 0x7f, 0x0, 0x80, 0x0, 0x80, 0x1, 0x0, + 0x1, 0x0, 0x1, 0x0, 0x2, 0x0, 0x2, 0x0, + 0x2, 0x0, 0x24, 0x0, 0x24, 0x0, 0xf4, 0x0, + 0x18, 0x0, 0x18, 0x0, 0x10, 0x0, + + /* U+3001 "、" */ + 0x47, 0x1c, 0x71, 0x0, + + /* U+3002 "。" */ + 0x74, 0x63, 0x17, 0x0, + + /* U+300A "《" */ + 0x12, 0x1a, 0x16, 0x34, 0x2c, 0x6c, 0x48, 0xd8, + 0x58, 0x48, 0x6c, 0x24, 0x34, 0x16, 0x1a, + + /* U+300B "》" */ + 0x48, 0x48, 0x68, 0x2c, 0x34, 0x36, 0x12, 0x1b, + 0x1a, 0x12, 0x36, 0x24, 0x2c, 0x68, 0x58, + + /* U+4E00 "一" */ + 0xff, 0xff, 0xff, 0xfc, + + /* U+4E08 "丈" */ + 0x0, 0xc0, 0x3, 0x0, 0xc, 0x3f, 0xff, 0xff, + 0xfc, 0x7, 0x3, 0x18, 0xc, 0x60, 0x19, 0x80, + 0x66, 0x0, 0xf0, 0x1, 0xc0, 0x1f, 0xc3, 0xe7, + 0xf6, 0x3, 0x80, + + /* U+4E2A "个" */ + 0x0, 0x80, 0x1, 0x80, 0x3, 0xc0, 0x7, 0x70, + 0x1e, 0x38, 0x79, 0x9e, 0x71, 0x8e, 0x1, 0x80, + 0x1, 0x80, 0x1, 0x80, 0x1, 0x80, 0x1, 0x80, + 0x1, 0x80, 0x1, 0x80, 0x1, 0x80, + + /* U+4E3B "主" */ + 0x6, 0x0, 0x1c, 0x0, 0x38, 0x1f, 0xfe, 0x7f, + 0xf8, 0xc, 0x0, 0x30, 0x0, 0xc0, 0x7f, 0xf1, + 0xff, 0xc0, 0x30, 0x0, 0xc0, 0x3, 0x3, 0xff, + 0xff, 0xff, 0xc0, + + /* U+4EB2 "亲" */ + 0x3, 0x0, 0xc, 0x7, 0xff, 0x86, 0x18, 0x18, + 0x63, 0xff, 0xf0, 0x30, 0x0, 0xc0, 0xff, 0xfc, + 0xc, 0x83, 0x37, 0xc, 0xce, 0x63, 0x19, 0x3c, + 0x0, + + /* U+4ECE "从" */ + 0x18, 0x60, 0x30, 0xc0, 0x61, 0x80, 0xc3, 0x1, + 0x86, 0x3, 0xc, 0x7, 0x38, 0x1e, 0x70, 0x36, + 0xf0, 0x65, 0xe1, 0xc6, 0xe3, 0xc, 0xce, 0x38, + 0xd8, 0x61, 0x90, 0x0, 0x0, + + /* U+4F2F "伯" */ + 0x18, 0xe0, 0x63, 0x1, 0x8c, 0xd, 0xff, 0x37, + 0xfd, 0xd8, 0x3f, 0x60, 0xfd, 0x83, 0x77, 0xfc, + 0xdf, 0xf3, 0x60, 0xcd, 0x83, 0x36, 0xc, 0xdf, + 0xf3, 0x7f, 0xc0, + + /* U+4F84 "侄" */ + 0x8, 0x0, 0x37, 0xfc, 0x63, 0x1, 0x8c, 0xc3, + 0x19, 0xce, 0x71, 0xbd, 0xff, 0xb9, 0x3, 0x70, + 0x64, 0x60, 0xc0, 0xcf, 0xf1, 0x83, 0x3, 0x6, + 0x6, 0xc, 0xd, 0xff, 0x80, + + /* U+513F "儿" */ + 0xc, 0x30, 0xc, 0x30, 0xc, 0x30, 0xc, 0x30, + 0xc, 0x30, 0xc, 0x30, 0xc, 0x30, 0xc, 0x30, + 0xc, 0x30, 0x18, 0x32, 0x18, 0x33, 0x38, 0x33, + 0x70, 0x3f, 0x60, 0x1e, + + /* U+5144 "兄" */ + 0x3f, 0xf8, 0x7f, 0xf0, 0xc0, 0x61, 0x80, 0xc3, + 0x1, 0x87, 0xff, 0xf, 0xfe, 0x6, 0x60, 0xc, + 0xc0, 0x19, 0x80, 0x63, 0x19, 0xc6, 0x3f, 0xf, + 0xcc, 0xf, 0x80, + + /* U+5165 "入" */ + 0xc, 0x0, 0x1c, 0x0, 0x18, 0x0, 0x18, 0x0, + 0x38, 0x0, 0xf0, 0x1, 0xb0, 0x3, 0x60, 0xc, + 0x60, 0x38, 0xe0, 0x60, 0xe3, 0x80, 0xef, 0x1, + 0xc8, 0x0, 0x80, + + /* U+516C "公" */ + 0x1c, 0xc0, 0x30, 0xc0, 0xe1, 0xc1, 0x81, 0xc7, + 0x21, 0xdc, 0xe1, 0x91, 0x82, 0x7, 0x0, 0xc, + 0x60, 0x30, 0xe0, 0xc0, 0xc3, 0xff, 0xc7, 0xff, + 0x88, 0x1, 0x80, + + /* U+5173 "关" */ + 0x30, 0x30, 0x30, 0xc0, 0x61, 0x83, 0xff, 0xc7, + 0xff, 0x80, 0x60, 0x0, 0xc0, 0x7f, 0xfe, 0xff, + 0xfc, 0xf, 0x0, 0x1f, 0x0, 0xf7, 0x7, 0xc7, + 0x9e, 0x7, 0x90, 0x0, 0x0, + + /* U+517B "养" */ + 0x8, 0x60, 0x30, 0xc1, 0xff, 0xe0, 0x18, 0x0, + 0x30, 0x7, 0xff, 0x1, 0x80, 0x7f, 0xfe, 0x38, + 0x71, 0xf1, 0xf9, 0x63, 0x30, 0xc6, 0x3, 0x8c, + 0xe, 0x18, 0x18, 0x30, 0x0, + + /* U+518D "再" */ + 0x7f, 0xfc, 0xff, 0xf8, 0xc, 0x1, 0xff, 0xc3, + 0x31, 0x86, 0x63, 0xf, 0xfe, 0x19, 0x8c, 0x33, + 0x19, 0xff, 0xff, 0xff, 0xf9, 0x80, 0xc3, 0x1, + 0x86, 0xf, 0x0, + + /* U+52A9 "助" */ + 0x0, 0x60, 0xfc, 0xc1, 0x99, 0x83, 0x33, 0x6, + 0x7f, 0xef, 0xff, 0xd9, 0x99, 0xb3, 0x33, 0x7e, + 0x66, 0xcc, 0xcd, 0x9f, 0x1f, 0xfe, 0x3f, 0x1c, + 0x60, 0x77, 0x80, 0x4f, 0x0, + + /* U+5355 "单" */ + 0x0, 0x0, 0x61, 0xc0, 0xc6, 0x1f, 0xfe, 0x63, + 0x19, 0x8c, 0x67, 0xff, 0x98, 0xc6, 0x63, 0x19, + 0xff, 0xe0, 0x30, 0x3f, 0xff, 0xff, 0xfc, 0xc, + 0x0, 0x30, 0x0, + + /* U+53D4 "叔" */ + 0x30, 0x0, 0x63, 0xf0, 0xc7, 0xf1, 0xff, 0xe3, + 0xc, 0xc6, 0x19, 0xbf, 0xd3, 0x7f, 0xb4, 0x18, + 0x78, 0xbc, 0xf3, 0x68, 0xc6, 0xd9, 0x8d, 0xb7, + 0x83, 0x39, 0x8c, 0x61, 0x80, + + /* U+54E5 "哥" */ + 0xff, 0xfc, 0x0, 0x43, 0xf9, 0x8, 0x64, 0x21, + 0x90, 0xfe, 0x40, 0x1, 0x3f, 0xff, 0x0, 0x10, + 0xfe, 0x42, 0x19, 0x8, 0x64, 0x3f, 0x90, 0x1, + 0xc0, + + /* U+5668 "器" */ + 0x7d, 0xf9, 0x96, 0x66, 0x59, 0x9f, 0x7e, 0x6, + 0x60, 0x19, 0xcf, 0xff, 0xc6, 0x38, 0x38, 0x73, + 0xc0, 0xef, 0xdf, 0xd9, 0x66, 0x65, 0x99, 0xf7, + 0xe0, + + /* U+56DE "回" */ + 0xff, 0xff, 0xff, 0xfc, 0x0, 0xf0, 0x3, 0xdf, + 0xcf, 0x63, 0x3d, 0x8c, 0xf6, 0x33, 0xd8, 0xcf, + 0x7f, 0x3c, 0x0, 0xf0, 0x3, 0xff, 0xff, 0xff, + 0xf0, + + /* U+5802 "堂" */ + 0x3, 0x18, 0x66, 0x30, 0xcc, 0xc7, 0xff, 0xfc, + 0x0, 0x78, 0x0, 0xf7, 0xfd, 0x88, 0x18, 0x1f, + 0xf0, 0x6, 0x0, 0xc, 0x1, 0xff, 0xc0, 0x30, + 0x0, 0x60, 0x3f, 0xff, 0x80, + + /* U+5916 "外" */ + 0x18, 0x60, 0x30, 0xc0, 0xc1, 0x81, 0xfb, 0x3, + 0xf6, 0xc, 0x6c, 0x18, 0xde, 0x6b, 0xbe, 0x7e, + 0x6e, 0x1c, 0xc8, 0x31, 0x80, 0xe3, 0x3, 0x86, + 0x1e, 0xc, 0x10, 0x18, 0x0, + + /* U+592B "夫" */ + 0x3, 0x0, 0x6, 0x0, 0xc, 0x3, 0xff, 0xc7, + 0xff, 0x80, 0x60, 0x0, 0xc0, 0x7f, 0xfe, 0xff, + 0xfc, 0xf, 0x0, 0x1e, 0x0, 0x76, 0x3, 0xcf, + 0x1f, 0xf, 0xb8, 0x7, 0x0, + + /* U+5973 "女" */ + 0x4, 0x0, 0x18, 0x0, 0x60, 0x3, 0x0, 0xff, + 0xff, 0xff, 0xf1, 0x86, 0x6, 0x18, 0x38, 0xe0, + 0xf3, 0x1, 0xfc, 0x1, 0xf0, 0xf, 0xf3, 0xf9, + 0xff, 0x1, 0x80, + + /* U+5976 "奶" */ + 0x30, 0x0, 0x60, 0x0, 0x8f, 0xe7, 0xec, 0xcf, + 0xd9, 0x8d, 0xb7, 0x1b, 0x6f, 0xe6, 0xc3, 0xf9, + 0x86, 0xf3, 0xc, 0x6c, 0x19, 0xf8, 0x37, 0xb0, + 0x7c, 0xc7, 0x90, 0x8f, 0x0, + + /* U+59AF "妯" */ + 0x60, 0x61, 0x81, 0x86, 0x6, 0x18, 0x18, 0xfb, + 0xff, 0xef, 0xf5, 0xb6, 0xd6, 0xdb, 0xdb, 0x6f, + 0x4f, 0xf7, 0x36, 0xce, 0xdb, 0x7b, 0xff, 0x2f, + 0xfc, 0x30, 0xc0, + + /* U+59B9 "妹" */ + 0x60, 0x60, 0xc0, 0xc1, 0x8f, 0xf7, 0xdf, 0xef, + 0x86, 0xb, 0xc, 0x16, 0x18, 0x6f, 0xff, 0xd8, + 0xf0, 0xe1, 0xe0, 0xc7, 0xe1, 0xdb, 0x66, 0xf6, + 0xf8, 0xcc, 0xa0, 0x18, 0x0, + + /* U+59BB "妻" */ + 0x3, 0x0, 0xc, 0xf, 0xff, 0xc0, 0xc0, 0x7f, + 0xf0, 0xc, 0x4f, 0xff, 0xc0, 0xc4, 0x7f, 0xf0, + 0x18, 0xf, 0xff, 0xc6, 0x18, 0x3f, 0xc0, 0x1f, + 0x8f, 0xe7, 0x80, + + /* U+59D0 "姐" */ + 0x60, 0x1, 0x8f, 0xe6, 0x3f, 0x98, 0xc6, 0xfb, + 0x1b, 0xec, 0x65, 0xbf, 0xb6, 0xc6, 0xdb, 0x1b, + 0xcf, 0xe7, 0x31, 0x9c, 0xc6, 0x7b, 0x1b, 0xff, + 0xfc, 0xff, 0xc0, 0x0, + + /* U+59D1 "姑" */ + 0x30, 0x30, 0x60, 0x60, 0xc0, 0xc1, 0x81, 0x8f, + 0xff, 0xff, 0xff, 0xdb, 0xc, 0x36, 0x18, 0x6c, + 0x31, 0xd3, 0xfc, 0xe6, 0x18, 0xcc, 0x33, 0xd8, + 0x7e, 0xbf, 0xd0, 0x61, 0x80, + + /* U+59E5 "姥" */ + 0x60, 0xc1, 0x83, 0x26, 0xc, 0xd8, 0xfe, 0xf8, + 0xdb, 0xe3, 0xc5, 0xff, 0xd6, 0x30, 0xdb, 0x8b, + 0x5e, 0xf7, 0xff, 0xd, 0x70, 0x79, 0x85, 0xe6, + 0x3c, 0xf, 0xc0, + + /* U+59E8 "姨" */ + 0x60, 0x61, 0x9f, 0xf6, 0x7f, 0xd8, 0x18, 0xfb, + 0xf9, 0x61, 0xa5, 0x86, 0x96, 0xfe, 0xde, 0x63, + 0x5f, 0xf7, 0xe, 0xcc, 0x3b, 0x79, 0xb3, 0x1c, + 0x78, 0x60, 0xc0, + + /* U+5A0C "娌" */ + 0x60, 0x0, 0xc7, 0xf9, 0x89, 0xb7, 0xd3, 0x6f, + 0xbf, 0xcb, 0x4d, 0x96, 0x9b, 0x6d, 0x36, 0xdb, + 0xfd, 0xa0, 0xc1, 0xcf, 0xf1, 0xc3, 0x7, 0x86, + 0x1f, 0xc, 0x31, 0xff, 0x80, + + /* U+5A7F "婿" */ + 0x60, 0x0, 0xcf, 0xfd, 0x81, 0x9b, 0x1b, 0x2f, + 0xb7, 0xcb, 0x7f, 0xd7, 0xbf, 0xae, 0x0, 0xdb, + 0xfd, 0xa6, 0x19, 0xcf, 0xf1, 0x98, 0x63, 0xbf, + 0xcd, 0x61, 0xb0, 0xc7, 0x0, + + /* U+5AB3 "媳" */ + 0x60, 0x60, 0xc0, 0xc1, 0x8f, 0xf7, 0xd8, 0x6f, + 0xbf, 0xcb, 0x61, 0x96, 0xc3, 0x2d, 0xfe, 0xdb, + 0xd, 0xa7, 0xf9, 0xc1, 0x81, 0xaf, 0xe3, 0xfa, + 0x6c, 0xb3, 0xf1, 0x7e, 0x0, + + /* U+5AE1 "嫡" */ + 0x60, 0x60, 0xc0, 0x61, 0x9f, 0xfb, 0xc, 0x6f, + 0x88, 0x8b, 0x7f, 0xd6, 0xc9, 0xed, 0xff, 0xdb, + 0x26, 0xe7, 0xfc, 0xcf, 0x79, 0xdf, 0xf7, 0xbc, + 0x69, 0x60, 0xf0, 0xc3, 0x80, + + /* U+5B50 "子" */ + 0x3f, 0xf8, 0x7f, 0xf0, 0x1, 0xc0, 0xf, 0x0, + 0x18, 0x0, 0x30, 0x3f, 0xff, 0xff, 0xff, 0x1, + 0x80, 0x3, 0x0, 0x6, 0x0, 0xc, 0x0, 0xf8, + 0x1, 0xe0, 0x0, + + /* U+5B59 "孙" */ + 0x0, 0x61, 0xf8, 0xc0, 0x31, 0x80, 0xcb, 0x43, + 0x16, 0xc6, 0x2d, 0x8e, 0xdb, 0x1f, 0xb6, 0xfb, + 0x65, 0xe4, 0xcc, 0xd9, 0x99, 0x93, 0x3, 0x6, + 0x6, 0xc, 0x1c, 0x70, 0x0, + + /* U+5B9A "定" */ + 0x1, 0x80, 0x1, 0x80, 0x7f, 0xfe, 0x7f, 0xfe, + 0x60, 0x6, 0x6f, 0xfe, 0xf, 0xf8, 0x1, 0x80, + 0x19, 0x80, 0x19, 0xf8, 0x19, 0xf8, 0x3d, 0x80, + 0x3f, 0x80, 0x67, 0xfe, 0x61, 0xfe, + + /* U+5C5E "属" */ + 0x3f, 0xfe, 0x60, 0x1c, 0xc0, 0x39, 0xff, 0xf3, + 0x7f, 0x86, 0xf8, 0xd, 0xff, 0x1a, 0x66, 0x37, + 0xfc, 0x61, 0x80, 0xbf, 0xfb, 0x66, 0xb6, 0xff, + 0x69, 0x81, 0xc0, + + /* U+5CB3 "岳" */ + 0x0, 0x70, 0xff, 0xc3, 0x0, 0xc, 0x0, 0x3f, + 0xfc, 0xc1, 0x83, 0x6, 0xc, 0x18, 0xff, 0xfc, + 0xc, 0x6, 0x31, 0x98, 0xc6, 0x63, 0x19, 0xff, + 0xe0, 0x1, 0x80, + + /* U+5DF1 "己" */ + 0xff, 0xe7, 0xff, 0x0, 0x18, 0x0, 0xc0, 0x6, + 0x7f, 0xf3, 0xff, 0x98, 0xc, 0xc0, 0x6, 0x0, + 0xb0, 0x7, 0x80, 0x7f, 0xff, 0x3f, 0xf8, + + /* U+5E2E "帮" */ + 0x18, 0x3, 0xff, 0xff, 0xfd, 0xc6, 0x36, 0x7f, + 0xd8, 0x63, 0x3f, 0xfc, 0xce, 0x3f, 0xf3, 0x1, + 0x8c, 0x3, 0xff, 0x88, 0xc6, 0x23, 0x18, 0x8d, + 0xe0, 0x30, 0x0, + + /* U+5EB6 "庶" */ + 0x1, 0x80, 0x6, 0x7, 0xff, 0xdf, 0xff, 0x60, + 0x1, 0x98, 0xc7, 0xff, 0xd9, 0x8c, 0x66, 0x31, + 0x98, 0xc6, 0x7f, 0x18, 0x2, 0xcb, 0x5b, 0x6d, + 0xbd, 0xb6, 0xc0, 0x0, + + /* U+5F1F "弟" */ + 0x38, 0x60, 0x61, 0x87, 0xff, 0x80, 0xc6, 0x3, + 0x19, 0xff, 0xe6, 0x30, 0x18, 0xc0, 0x63, 0x1, + 0xff, 0xf0, 0xf0, 0xcf, 0xc7, 0xf3, 0x39, 0xc, + 0x0, + + /* U+62E9 "择" */ + 0x20, 0x0, 0x47, 0xf8, 0x86, 0x77, 0xcf, 0xcf, + 0x8f, 0x4, 0x3f, 0x9, 0xe7, 0x9f, 0x36, 0x78, + 0x61, 0xc7, 0xf8, 0x81, 0x81, 0x3, 0x2, 0x7f, + 0xfc, 0xc, 0x18, 0x18, 0x0, + + /* U+6301 "持" */ + 0x30, 0x60, 0x60, 0xc0, 0xc1, 0x81, 0x9f, 0xef, + 0x86, 0x1f, 0xc, 0xd, 0xff, 0x9c, 0xc, 0x38, + 0x19, 0xef, 0xfc, 0xc4, 0x61, 0x8c, 0xc3, 0x19, + 0x9e, 0xf, 0x18, 0x1e, 0x0, + + /* U+652F "支" */ + 0x1, 0x80, 0x1, 0x80, 0x1, 0x80, 0x7f, 0xfe, + 0x7f, 0xfe, 0x1, 0x80, 0x3f, 0xfc, 0x3f, 0xfc, + 0x18, 0x18, 0xc, 0x30, 0x6, 0x70, 0x3, 0xe0, + 0x7, 0xf0, 0xfe, 0x7f, 0x78, 0x1e, + + /* U+660E "明" */ + 0xfd, 0xff, 0x36, 0x3c, 0xd8, 0xf3, 0x63, 0xcd, + 0xff, 0xf7, 0xfc, 0xd8, 0xf3, 0x63, 0xcd, 0xff, + 0xf7, 0xf0, 0x38, 0xc0, 0xc3, 0x7, 0x3c, 0x8, + 0xe0, + + /* U+662F "是" */ + 0x3f, 0xfc, 0x60, 0x18, 0xff, 0xf1, 0x80, 0x63, + 0xff, 0xc0, 0x0, 0x0, 0x0, 0x3f, 0xff, 0x19, + 0x80, 0x33, 0xf8, 0x76, 0x1, 0xfc, 0x6, 0x7f, + 0xec, 0x3f, 0xc0, + + /* U+663E "显" */ + 0x3f, 0xf8, 0x60, 0x30, 0xff, 0xe1, 0xff, 0xc3, + 0x1, 0x87, 0xff, 0x0, 0x0, 0x3, 0x60, 0x66, + 0xcc, 0x6d, 0xb0, 0xdb, 0x60, 0x36, 0xf, 0xff, + 0xff, 0xff, 0xc0, + + /* U+66FE "曾" */ + 0x30, 0xc1, 0x88, 0xff, 0xfd, 0x6b, 0xd6, 0xbd, + 0x6b, 0xff, 0xf0, 0x0, 0x7f, 0xe6, 0x6, 0x7f, + 0xe6, 0x6, 0x7f, 0xe6, 0x6, + + /* U+672A "未" */ + 0x1, 0x80, 0x3, 0x0, 0x6, 0x1, 0xff, 0xe3, + 0xff, 0xc0, 0x30, 0x0, 0x60, 0x3f, 0xff, 0x7f, + 0xfe, 0x7, 0x80, 0x1f, 0x80, 0xed, 0xc7, 0x99, + 0xcc, 0x31, 0xc0, 0x60, 0x0, + + /* U+679C "果" */ + 0x3f, 0xf8, 0x8c, 0xe2, 0x33, 0x8f, 0xfe, 0x23, + 0x38, 0x8c, 0xe3, 0xff, 0x80, 0xc0, 0xff, 0xff, + 0xff, 0xf0, 0xfc, 0xe, 0xdc, 0xf3, 0x1c, 0xc, + 0x0, + + /* U+67E5 "查" */ + 0x1, 0x80, 0x1, 0x80, 0x7f, 0xfe, 0x7, 0xe0, + 0xd, 0xb0, 0x39, 0xbe, 0x70, 0xe, 0x7f, 0xf8, + 0x18, 0x18, 0x1f, 0xf8, 0x18, 0x18, 0x1f, 0xf8, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe, + + /* U+683C "格" */ + 0x30, 0xc0, 0x63, 0xf8, 0xc7, 0xff, 0xdc, 0x6f, + 0xf9, 0x86, 0x5f, 0x1e, 0x1c, 0x3f, 0xfe, 0x7f, + 0x9f, 0xee, 0xf, 0xcf, 0xf5, 0x98, 0x63, 0x30, + 0xc6, 0x7f, 0x8c, 0xc3, 0x0, + + /* U+6BCD "母" */ + 0x3f, 0xf8, 0xc8, 0x63, 0x71, 0x8c, 0xe6, 0x31, + 0x1b, 0xff, 0xff, 0xff, 0xcc, 0x8c, 0x67, 0x31, + 0x84, 0xc7, 0xff, 0xc0, 0xc, 0x0, 0x60, 0x7, + 0x80, + + /* U+6E05 "清" */ + 0x0, 0xc0, 0xc1, 0x81, 0xdf, 0xf1, 0x86, 0x0, + 0x7f, 0xc8, 0x18, 0x3b, 0xff, 0xb0, 0x0, 0x7, + 0xfc, 0xc, 0x18, 0xdf, 0xf1, 0xb0, 0x66, 0x7f, + 0xcc, 0xc1, 0x91, 0x8e, 0x0, + + /* U+7236 "父" */ + 0x0, 0x0, 0xe, 0x70, 0xc, 0x38, 0x18, 0x1c, + 0x38, 0xe, 0x7c, 0x36, 0x2c, 0x30, 0x6, 0x70, + 0x7, 0x60, 0x3, 0xc0, 0x3, 0xc0, 0x7, 0xe0, + 0xe, 0xf8, 0x7c, 0x3f, 0x70, 0xe, + + /* U+7237 "爷" */ + 0x0, 0x0, 0x1c, 0xe1, 0xf0, 0xf3, 0xf3, 0xe0, + 0x3e, 0x1, 0xfc, 0x3f, 0x9f, 0xb8, 0x6, 0x3f, + 0xf8, 0x7f, 0xf0, 0xc, 0x60, 0x18, 0xc0, 0x37, + 0x80, 0x6e, 0x0, 0xc0, 0x0, + + /* U+7384 "玄" */ + 0x6, 0x0, 0xc, 0xf, 0xff, 0xff, 0xff, 0x6, + 0x0, 0x38, 0x1, 0xc6, 0xe, 0x38, 0x7f, 0xc0, + 0x1e, 0x80, 0xe3, 0x7, 0xe, 0x7f, 0xfd, 0xfc, + 0x30, 0x0, 0x80, + + /* U+7525 "甥" */ + 0x5b, 0xfe, 0xb6, 0x4d, 0xff, 0xff, 0xff, 0xfd, + 0xb2, 0x63, 0x7f, 0xdf, 0x18, 0x3e, 0x30, 0x1b, + 0xfe, 0x31, 0xcc, 0xf3, 0x1f, 0xee, 0x3e, 0x38, + 0x60, 0xc7, 0x80, + + /* U+754C "界" */ + 0x3f, 0xf8, 0x63, 0x30, 0xc6, 0x61, 0xff, 0xc3, + 0x19, 0x86, 0x33, 0xf, 0xfe, 0x7, 0x30, 0x3c, + 0x39, 0xec, 0xf9, 0x39, 0x90, 0x63, 0x3, 0xc6, + 0x6, 0xc, 0x0, + + /* U+7684 "的" */ + 0x30, 0xc0, 0xc3, 0xf, 0xdf, 0xff, 0x7f, 0xcf, + 0xf, 0x3c, 0x3c, 0xc8, 0xff, 0x33, 0xcc, 0xef, + 0x31, 0xbc, 0xc0, 0xff, 0x7, 0xc0, 0xfb, 0x1, + 0xe0, + + /* U+77E5 "知" */ + 0x70, 0x1, 0x83, 0xf7, 0xff, 0xdf, 0xf3, 0xd8, + 0xcf, 0x63, 0x31, 0x8c, 0xff, 0xf3, 0xff, 0xcc, + 0x63, 0x31, 0xcc, 0xcd, 0xb3, 0x37, 0xfd, 0x8b, + 0xfc, 0xc, 0xc0, + + /* U+786E "确" */ + 0x0, 0x3, 0xf6, 0x3, 0x1f, 0x98, 0xcc, 0x63, + 0x31, 0x9f, 0xf7, 0xd6, 0xff, 0x5b, 0xef, 0xff, + 0xbd, 0xbe, 0xff, 0xdb, 0xff, 0x7f, 0x6d, 0x89, + 0xb0, 0x61, 0xc0, + + /* U+793A "示" */ + 0x3f, 0xfc, 0x7f, 0xf8, 0x0, 0x0, 0x0, 0x7, + 0xff, 0xef, 0xff, 0xc0, 0x60, 0xc, 0xcc, 0x19, + 0x98, 0x63, 0x19, 0xc6, 0x3b, 0xc, 0x30, 0x78, + 0x0, 0xf0, 0x0, + + /* U+7956 "祖" */ + 0x10, 0x0, 0x33, 0xf8, 0x67, 0xf7, 0xfc, 0x60, + 0xd8, 0xc1, 0xb1, 0x87, 0x7f, 0x1e, 0xc6, 0x7f, + 0x8d, 0xbb, 0xf8, 0x66, 0x30, 0xcc, 0x61, 0x98, + 0xc3, 0x7f, 0xc6, 0xff, 0x80, + + /* U+7A7A "空" */ + 0x3, 0x0, 0xc, 0xf, 0xff, 0xf0, 0x3, 0xcc, + 0xec, 0xf1, 0xcf, 0x83, 0xd8, 0x2, 0x3f, 0xf0, + 0xc, 0x0, 0x30, 0x0, 0xc0, 0xff, 0xfc, + + /* U+7B97 "算" */ + 0x30, 0xc1, 0xff, 0xfd, 0xb3, 0xf, 0xfe, 0x30, + 0x18, 0xff, 0xe3, 0x1, 0x8f, 0xfe, 0x3f, 0xf8, + 0x31, 0x80, 0xc6, 0x3f, 0xff, 0x38, 0x61, 0x81, + 0x80, + + /* U+7CFB "系" */ + 0x0, 0xf1, 0xff, 0xc0, 0xe4, 0x6, 0x38, 0x7f, + 0xc0, 0x3d, 0x83, 0x83, 0x1f, 0xfe, 0x63, 0xc, + 0xcc, 0x87, 0x37, 0x38, 0xc7, 0xcf, 0x8, 0x3c, + 0x0, + + /* U+7ED3 "结" */ + 0x30, 0x70, 0x60, 0xe0, 0xcf, 0xfb, 0x5f, 0xf6, + 0xc7, 0x1f, 0x8e, 0x3e, 0xff, 0x99, 0xff, 0x60, + 0x1, 0xfb, 0xfa, 0x6, 0x30, 0x2c, 0x67, 0xd8, + 0xdf, 0x3f, 0x80, 0x63, 0x0, + + /* U+7EE7 "继" */ + 0x10, 0x30, 0x66, 0x60, 0xce, 0xdb, 0x9b, 0xa6, + 0xb7, 0xdd, 0xe6, 0x3e, 0xff, 0x99, 0xbc, 0x67, + 0x7d, 0xf7, 0xed, 0xe, 0xd0, 0xf9, 0x8f, 0xf0, + 0x1e, 0x7f, 0xc0, + + /* U+81EA "自" */ + 0x6, 0x0, 0xe0, 0xff, 0xff, 0xff, 0xc0, 0x3c, + 0x3, 0xff, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, + 0xfc, 0x3, 0xc0, 0x3f, 0xff, 0xff, 0xf0, + + /* U+8205 "舅" */ + 0x7f, 0xfb, 0xff, 0xdf, 0xfe, 0xff, 0xf0, 0x0, + 0x3f, 0xfd, 0x8c, 0x6f, 0xff, 0x63, 0x18, 0xff, + 0x1f, 0xfe, 0x1c, 0x31, 0xc1, 0xf8, 0x38, + + /* U+83DC "菜" */ + 0x18, 0x63, 0xff, 0xf1, 0x86, 0x0, 0x1c, 0x3, + 0xf9, 0xff, 0xc6, 0x21, 0x8c, 0xcc, 0x33, 0x0, + 0xc, 0xf, 0xff, 0xc3, 0xf0, 0x7b, 0x73, 0xcc, + 0xf0, 0x30, 0x0, + + /* U+8868 "表" */ + 0x1, 0x80, 0x3f, 0xfc, 0x3f, 0xfc, 0x1, 0x80, + 0x3f, 0xfc, 0x1, 0x80, 0x1, 0x80, 0x7f, 0xfe, + 0x7, 0xc4, 0x1c, 0xce, 0x7c, 0x78, 0x6c, 0x30, + 0xf, 0xbc, 0x1f, 0x8e, 0xc, 0x6, + + /* U+8BA1 "计" */ + 0x20, 0x60, 0xe0, 0xc0, 0xe1, 0x80, 0x83, 0x0, + 0x6, 0x1e, 0xff, 0xfd, 0xff, 0x98, 0x30, 0x30, + 0x60, 0x60, 0xc0, 0xd1, 0x81, 0xe3, 0x3, 0x86, + 0xe, 0xc, 0x8, 0x18, 0x0, + + /* U+8BE2 "询" */ + 0x21, 0x80, 0xe3, 0x0, 0x4f, 0xf8, 0x30, 0x3f, + 0x7f, 0x7e, 0x7e, 0xcc, 0xcd, 0x99, 0xfb, 0x33, + 0x36, 0x66, 0x6c, 0xff, 0xd9, 0xd8, 0x77, 0x3, + 0xc0, 0x7, 0x80, + + /* U+8BF4 "说" */ + 0x23, 0x18, 0xe6, 0x30, 0xe6, 0xc0, 0xbf, 0xe0, + 0x7f, 0xde, 0xc1, 0x8d, 0xff, 0x1b, 0xfe, 0x31, + 0xb0, 0x73, 0x60, 0xfc, 0xd3, 0xd9, 0xb6, 0xe3, + 0xe0, 0x87, 0x80, + + /* U+8BF7 "请" */ + 0x0, 0x60, 0xc0, 0xc1, 0xdf, 0xf9, 0x83, 0x1, + 0x3f, 0xc0, 0xc, 0x3d, 0xff, 0xf8, 0x0, 0x33, + 0xfc, 0x66, 0x18, 0xcf, 0xf1, 0xd8, 0x63, 0xbf, + 0xc6, 0x61, 0x88, 0xce, 0x0, + + /* U+8F93 "输" */ + 0x20, 0x60, 0xc1, 0xc3, 0xe7, 0xc7, 0xfc, 0xe4, + 0x7f, 0xee, 0x0, 0x3c, 0xf1, 0x7d, 0xea, 0xfb, + 0xd4, 0x67, 0xab, 0xef, 0x57, 0x9e, 0xa3, 0x34, + 0x46, 0x68, 0x8c, 0xf7, 0x0, + + /* U+8FD4 "返" */ + 0x0, 0xc, 0x63, 0xfe, 0x33, 0x0, 0x3, 0xfe, + 0x3, 0xfe, 0xf3, 0xc, 0xf3, 0xec, 0x33, 0x78, + 0x36, 0x38, 0x36, 0xfc, 0x3e, 0xce, 0x78, 0x0, + 0xff, 0xfe, 0x47, 0xfe, + + /* U+8FD9 "这" */ + 0x40, 0xc1, 0xc1, 0x81, 0xff, 0xf9, 0x7f, 0xf0, + 0x21, 0x9e, 0xf6, 0x3c, 0x7c, 0x18, 0x70, 0x31, + 0xf8, 0x6f, 0x38, 0xd8, 0x33, 0xe0, 0xf, 0xff, + 0xe8, 0xff, 0xc0, + + /* U+9009 "选" */ + 0x0, 0x60, 0xc6, 0xc1, 0xcd, 0x81, 0xbf, 0xe0, + 0x66, 0x0, 0xff, 0xfd, 0xff, 0xf8, 0xd8, 0x31, + 0xb0, 0x63, 0x68, 0xce, 0xc9, 0xb8, 0xf3, 0xa1, + 0xfd, 0xff, 0xd0, 0xff, 0x80, + + /* U+9664 "除" */ + 0x0, 0x41, 0xf0, 0xc3, 0x63, 0xc6, 0xcc, 0xcd, + 0x31, 0xde, 0xe1, 0xfc, 0xfe, 0x6c, 0x30, 0xdf, + 0xff, 0xbf, 0xff, 0xed, 0xa6, 0x1b, 0x6c, 0x66, + 0xd9, 0x8c, 0xf0, 0x78, 0x0, + + /* U+9762 "面" */ + 0xff, 0xff, 0xff, 0xf0, 0x30, 0x1f, 0xfe, 0x7f, + 0xf9, 0x33, 0x64, 0xcd, 0x93, 0xf6, 0x4c, 0xd9, + 0x3f, 0x64, 0xcd, 0x93, 0x36, 0x7f, 0xf9, 0xff, + 0xe0, + + /* U+FF01 "!" */ + 0xff, 0xff, 0xfa, 0x1f, 0xf0, + + /* U+FF0C "," */ + 0xdf, 0xbd, 0x80, + + /* U+FF1A ":" */ + 0xff, 0x80, 0x0, 0xff, 0x80, + + /* U+FF1B ";" */ + 0xff, 0x80, 0x7, 0xfc, 0xbd, 0x0, + + /* U+FF1F "?" */ + 0x3c, 0xfd, 0x18, 0x30, 0xe3, 0x86, 0x8, 0x0, + 0x70, 0xe1, 0xc0 +}; + + +/*--------------------- + * GLYPH DESCRIPTION + *--------------------*/ + +static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = { + {.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */, + {.bitmap_index = 0, .adv_w = 58, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1, .adv_w = 95, .box_w = 3, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 6, .adv_w = 147, .box_w = 6, .box_h = 5, .ofs_x = 1, .ofs_y = 8}, + {.bitmap_index = 10, .adv_w = 151, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 22, .adv_w = 151, .box_w = 7, .box_h = 15, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 36, .adv_w = 246, .box_w = 15, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 59, .adv_w = 189, .box_w = 11, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 76, .adv_w = 83, .box_w = 2, .box_h = 5, .ofs_x = 1, .ofs_y = 8}, + {.bitmap_index = 78, .adv_w = 97, .box_w = 4, .box_h = 16, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 86, .adv_w = 97, .box_w = 4, .box_h = 16, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 94, .adv_w = 130, .box_w = 6, .box_h = 6, .ofs_x = 1, .ofs_y = 7}, + {.bitmap_index = 99, .adv_w = 151, .box_w = 8, .box_h = 8, .ofs_x = 1, .ofs_y = 2}, + {.bitmap_index = 107, .adv_w = 83, .box_w = 4, .box_h = 5, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 110, .adv_w = 95, .box_w = 4, .box_h = 2, .ofs_x = 1, .ofs_y = 4}, + {.bitmap_index = 111, .adv_w = 83, .box_w = 3, .box_h = 3, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 113, .adv_w = 99, .box_w = 6, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 125, .adv_w = 151, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 137, .adv_w = 151, .box_w = 7, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 148, .adv_w = 151, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 160, .adv_w = 151, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 172, .adv_w = 151, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 186, .adv_w = 151, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 198, .adv_w = 151, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 210, .adv_w = 151, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 222, .adv_w = 151, .box_w = 7, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 233, .adv_w = 151, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 245, .adv_w = 83, .box_w = 3, .box_h = 9, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 249, .adv_w = 83, .box_w = 3, .box_h = 12, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 254, .adv_w = 151, .box_w = 8, .box_h = 8, .ofs_x = 1, .ofs_y = 2}, + {.bitmap_index = 262, .adv_w = 151, .box_w = 8, .box_h = 6, .ofs_x = 1, .ofs_y = 3}, + {.bitmap_index = 268, .adv_w = 151, .box_w = 8, .box_h = 8, .ofs_x = 1, .ofs_y = 2}, + {.bitmap_index = 276, .adv_w = 132, .box_w = 7, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 287, .adv_w = 258, .box_w = 15, .box_h = 16, .ofs_x = 1, .ofs_y = -4}, + {.bitmap_index = 317, .adv_w = 164, .box_w = 10, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 332, .adv_w = 174, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 346, .adv_w = 168, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 360, .adv_w = 183, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 374, .adv_w = 157, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 386, .adv_w = 150, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 398, .adv_w = 184, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 412, .adv_w = 194, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 426, .adv_w = 84, .box_w = 2, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 429, .adv_w = 145, .box_w = 7, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 440, .adv_w = 176, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 454, .adv_w = 148, .box_w = 7, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 465, .adv_w = 219, .box_w = 11, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 482, .adv_w = 192, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 496, .adv_w = 197, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 511, .adv_w = 171, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 523, .adv_w = 197, .box_w = 11, .box_h = 15, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 544, .adv_w = 175, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 558, .adv_w = 160, .box_w = 9, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 572, .adv_w = 160, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 586, .adv_w = 191, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 600, .adv_w = 158, .box_w = 10, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 615, .adv_w = 234, .box_w = 14, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 636, .adv_w = 161, .box_w = 10, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 651, .adv_w = 149, .box_w = 10, .box_h = 12, .ofs_x = -1, .ofs_y = 0}, + {.bitmap_index = 666, .adv_w = 157, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 678, .adv_w = 97, .box_w = 4, .box_h = 15, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 686, .adv_w = 99, .box_w = 6, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 698, .adv_w = 97, .box_w = 4, .box_h = 15, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 706, .adv_w = 151, .box_w = 7, .box_h = 7, .ofs_x = 1, .ofs_y = 5}, + {.bitmap_index = 713, .adv_w = 145, .box_w = 9, .box_h = 2, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 716, .adv_w = 160, .box_w = 4, .box_h = 4, .ofs_x = 2, .ofs_y = 10}, + {.bitmap_index = 718, .adv_w = 151, .box_w = 7, .box_h = 9, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 726, .adv_w = 165, .box_w = 8, .box_h = 13, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 739, .adv_w = 135, .box_w = 7, .box_h = 9, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 747, .adv_w = 165, .box_w = 8, .box_h = 13, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 760, .adv_w = 149, .box_w = 8, .box_h = 9, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 769, .adv_w = 95, .box_w = 6, .box_h = 13, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 779, .adv_w = 153, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 791, .adv_w = 164, .box_w = 8, .box_h = 13, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 804, .adv_w = 78, .box_w = 2, .box_h = 13, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 808, .adv_w = 78, .box_w = 4, .box_h = 16, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 816, .adv_w = 155, .box_w = 8, .box_h = 13, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 829, .adv_w = 81, .box_w = 3, .box_h = 13, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 834, .adv_w = 247, .box_w = 12, .box_h = 9, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 848, .adv_w = 164, .box_w = 8, .box_h = 9, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 857, .adv_w = 160, .box_w = 8, .box_h = 9, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 866, .adv_w = 165, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 878, .adv_w = 165, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 890, .adv_w = 112, .box_w = 5, .box_h = 9, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 896, .adv_w = 127, .box_w = 6, .box_h = 9, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 903, .adv_w = 108, .box_w = 6, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 912, .adv_w = 163, .box_w = 8, .box_h = 9, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 921, .adv_w = 147, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 932, .adv_w = 221, .box_w = 13, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 947, .adv_w = 144, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 958, .adv_w = 147, .box_w = 9, .box_h = 12, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 972, .adv_w = 131, .box_w = 7, .box_h = 9, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 980, .adv_w = 97, .box_w = 5, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 990, .adv_w = 76, .box_w = 2, .box_h = 18, .ofs_x = 2, .ofs_y = -4}, + {.bitmap_index = 995, .adv_w = 97, .box_w = 5, .box_h = 15, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 1005, .adv_w = 151, .box_w = 8, .box_h = 3, .ofs_x = 1, .ofs_y = 4}, + {.bitmap_index = 1008, .adv_w = 256, .box_w = 10, .box_h = 10, .ofs_x = 3, .ofs_y = 1}, + {.bitmap_index = 1021, .adv_w = 256, .box_w = 11, .box_h = 10, .ofs_x = 2, .ofs_y = 2}, + {.bitmap_index = 1035, .adv_w = 256, .box_w = 6, .box_h = 5, .ofs_x = 10, .ofs_y = 9}, + {.bitmap_index = 1039, .adv_w = 256, .box_w = 6, .box_h = 5, .ofs_x = 1, .ofs_y = 8}, + {.bitmap_index = 1043, .adv_w = 256, .box_w = 14, .box_h = 7, .ofs_x = 1, .ofs_y = 3}, + {.bitmap_index = 1056, .adv_w = 256, .box_w = 8, .box_h = 15, .ofs_x = 4, .ofs_y = -1}, + {.bitmap_index = 1071, .adv_w = 256, .box_w = 14, .box_h = 7, .ofs_x = 1, .ofs_y = 3}, + {.bitmap_index = 1084, .adv_w = 256, .box_w = 8, .box_h = 15, .ofs_x = 4, .ofs_y = -1}, + {.bitmap_index = 1099, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1129, .adv_w = 256, .box_w = 5, .box_h = 5, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1133, .adv_w = 256, .box_w = 5, .box_h = 5, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1137, .adv_w = 256, .box_w = 8, .box_h = 15, .ofs_x = 8, .ofs_y = -1}, + {.bitmap_index = 1152, .adv_w = 256, .box_w = 8, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1167, .adv_w = 256, .box_w = 15, .box_h = 2, .ofs_x = 0, .ofs_y = 5}, + {.bitmap_index = 1171, .adv_w = 256, .box_w = 14, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1198, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1228, .adv_w = 256, .box_w = 14, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1255, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1280, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1309, .adv_w = 256, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1336, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1365, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1393, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1420, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1447, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1474, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 1503, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1532, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1559, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1588, .adv_w = 256, .box_w = 14, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1615, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1644, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1669, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1694, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1719, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1748, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1777, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1806, .adv_w = 256, .box_w = 14, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1833, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1862, .adv_w = 256, .box_w = 14, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1889, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1918, .adv_w = 256, .box_w = 14, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1945, .adv_w = 256, .box_w = 14, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 1973, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2002, .adv_w = 256, .box_w = 14, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2029, .adv_w = 256, .box_w = 14, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2056, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2085, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2114, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2143, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2172, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2199, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2228, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 2258, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 2285, .adv_w = 256, .box_w = 14, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2312, .adv_w = 256, .box_w = 13, .box_h = 14, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 2335, .adv_w = 256, .box_w = 14, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2362, .adv_w = 256, .box_w = 14, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 2390, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2415, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2444, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2473, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 2503, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2528, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 2555, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 2582, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 2603, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 2632, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2657, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 2687, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 2716, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2741, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 2770, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 2800, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 2829, .adv_w = 256, .box_w = 14, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2856, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 2883, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 2910, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2935, .adv_w = 256, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 2962, .adv_w = 256, .box_w = 14, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 2989, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3016, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3045, .adv_w = 256, .box_w = 14, .box_h = 13, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 3068, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 3093, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 3118, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3147, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 3174, .adv_w = 256, .box_w = 12, .box_h = 15, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 3197, .adv_w = 256, .box_w = 13, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 3220, .adv_w = 256, .box_w = 14, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 3247, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3277, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 3306, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3333, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 3360, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3389, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 3418, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3446, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3473, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3502, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 3531, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 3556, .adv_w = 256, .box_w = 3, .box_h = 12, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 3561, .adv_w = 256, .box_w = 3, .box_h = 6, .ofs_x = 3, .ofs_y = -2}, + {.bitmap_index = 3564, .adv_w = 256, .box_w = 3, .box_h = 11, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 3569, .adv_w = 256, .box_w = 3, .box_h = 14, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 3575, .adv_w = 256, .box_w = 7, .box_h = 12, .ofs_x = 0, .ofs_y = 0} +}; + +/*--------------------- + * CHARACTER MAPPING + *--------------------*/ + +static const uint16_t unicode_list_1[] = { + 0x0, 0x20, 0x1f45, 0x1f46, 0x20b9, 0x20ba, 0x20bb, 0x20bc, + 0x2143, 0x2f2a, 0x2f2b, 0x2f33, 0x2f34, 0x4d29, 0x4d31, 0x4d53, + 0x4d64, 0x4ddb, 0x4df7, 0x4e58, 0x4ead, 0x5068, 0x506d, 0x508e, + 0x5095, 0x509c, 0x50a4, 0x50b6, 0x51d2, 0x527e, 0x52fd, 0x540e, + 0x5591, 0x5607, 0x572b, 0x583f, 0x5854, 0x589c, 0x589f, 0x58d8, + 0x58e2, 0x58e4, 0x58f9, 0x58fa, 0x590e, 0x5911, 0x5935, 0x59a8, + 0x59dc, 0x5a0a, 0x5a79, 0x5a82, 0x5ac3, 0x5b87, 0x5bdc, 0x5d1a, + 0x5d57, 0x5ddf, 0x5e48, 0x6212, 0x622a, 0x6458, 0x6537, 0x6558, + 0x6567, 0x6627, 0x6653, 0x66c5, 0x670e, 0x6765, 0x6af6, 0x6d2e, + 0x715f, 0x7160, 0x72ad, 0x744e, 0x7475, 0x75ad, 0x770e, 0x7797, + 0x7863, 0x787f, 0x79a3, 0x7ac0, 0x7c24, 0x7dfc, 0x7e10, 0x8113, + 0x812e, 0x8305, 0x8791, 0x8aca, 0x8b0b, 0x8b1d, 0x8b20, 0x8ebc, + 0x8efd, 0x8f02, 0x8f32, 0x958d, 0x968b, 0xfe2a, 0xfe35, 0xfe43, + 0xfe44, 0xfe48 +}; + +/*Collect the unicode lists and glyph_id offsets*/ +static const lv_font_fmt_txt_cmap_t cmaps[] = +{ + { + .range_start = 32, .range_length = 95, .glyph_id_start = 1, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 215, .range_length = 65097, .glyph_id_start = 96, + .unicode_list = unicode_list_1, .glyph_id_ofs_list = NULL, .list_length = 106, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY + } +}; + + + +/*-------------------- + * ALL CUSTOM DATA + *--------------------*/ + +#if LVGL_VERSION_MAJOR == 8 +/*Store all the custom data of the font*/ +static lv_font_fmt_txt_glyph_cache_t cache; +#endif + +#if LVGL_VERSION_MAJOR >= 8 +static const lv_font_fmt_txt_dsc_t font_dsc = { +#else +static lv_font_fmt_txt_dsc_t font_dsc = { +#endif + .glyph_bitmap = glyph_bitmap, + .glyph_dsc = glyph_dsc, + .cmaps = cmaps, + .kern_dsc = NULL, + .kern_scale = 0, + .cmap_num = 2, + .bpp = 1, + .kern_classes = 0, + .bitmap_format = 0, +#if LVGL_VERSION_MAJOR == 8 + .cache = &cache +#endif +}; + + + +/*----------------- + * PUBLIC FONT + *----------------*/ + +/*Initialize a public general font descriptor*/ +#if LVGL_VERSION_MAJOR >= 8 +const lv_font_t lv_font_siyuan_16 = { +#else +lv_font_t lv_font_siyuan_16 = { +#endif + .get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/ + .get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/ + .line_height = 18, /*The maximum line height required by the font*/ + .base_line = 4, /*Baseline measured from the bottom of the line*/ +#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0) + .subpx = LV_FONT_SUBPX_NONE, +#endif +#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8 + .underline_position = -2, + .underline_thickness = 1, +#endif + .dsc = &font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */ +#if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9 + .fallback = NULL, +#endif + .user_data = NULL, +}; + + + +#endif /*#if LV_FONT_SIYUAN_16*/ + diff --git a/relation_calculator/img/show.gif b/relation_calculator/img/show.gif new file mode 100644 index 0000000000000000000000000000000000000000..52f591632accb17ec81ff775c040426bc73b12f9 Binary files /dev/null and b/relation_calculator/img/show.gif differ diff --git a/relation_calculator/relation_cal.c b/relation_calculator/relation_cal.c new file mode 100644 index 0000000000000000000000000000000000000000..ed8f086a404584405ff3584bce1b8716faeba950 --- /dev/null +++ b/relation_calculator/relation_cal.c @@ -0,0 +1,444 @@ +#include "relation_cal.h" +#include <string.h> + +#define SCREEN_WIDTH (lv_obj_get_width(lv_scr_act())) +#define SCREEN_HEIGHT (lv_obj_get_height(lv_scr_act())) + +#define DEMO_WIDTH (int32_t)((SCREEN_WIDTH * 0.85f)) +#define DEMO_HEIGHT (int32_t)(((DEMO_WIDTH) / 16.0f) * 9.0f) +#define DEMO_TITLE_HEIGHT (int32_t)(((DEMO_WIDTH) / 16.0f) * 1.2f) + +static relation_cal_t g_rel_cal; + +static const relation_transformation_t transitions[] = { + // Self - related relationships + {REL_SELF, REL_SELF, REL_SELF}, // Self to self remains self + {REL_SELF, REL_FATHER, REL_FATHER}, // Self to father is father + {REL_SELF, REL_MOTHER, REL_MOTHER}, // Self to mother is mother + {REL_SELF, REL_SON, REL_SON}, // Self to son is son + {REL_SELF, REL_DAUGHTER, REL_DAUGHTER}, // Self to daughter is daughter + {REL_SELF, REL_ELDER_BROTHER, REL_ELDER_BROTHER}, // Self to elder brother is elder brother + {REL_SELF, REL_YOUNGER_BROTHER, REL_YOUNGER_BROTHER}, // Self to younger brother is younger brother + {REL_SELF, REL_ELDER_SISTER, REL_ELDER_SISTER}, // Self to elder sister is elder sister + {REL_SELF, REL_YOUNGER_SISTER, REL_YOUNGER_SISTER}, // Self to younger sister is younger sister + + // Parent - child relationships + {REL_FATHER, REL_SON, REL_SELF}, // Father to son is self + {REL_FATHER, REL_DAUGHTER, REL_SELF}, // Father to daughter is self + {REL_FATHER, REL_WIFE, REL_MOTHER}, + {REL_MOTHER, REL_SON, REL_SELF}, // Mother to son is self + {REL_MOTHER, REL_DAUGHTER, REL_SELF}, // Mother to daughter is self + {REL_MOTHER, REL_HUSBAND, REL_FATHER}, + {REL_SON, REL_FATHER, REL_SELF}, // Son to father is self + {REL_SON, REL_MOTHER, REL_SELF}, // Son to mother is self + {REL_DAUGHTER, REL_FATHER, REL_SELF}, // Daughter to father is self + {REL_DAUGHTER, REL_MOTHER, REL_SELF}, // Daughter to mother is self + + // Grandparent relationships + {REL_FATHER, REL_FATHER, REL_GRANDFATHER}, // Father's father is grandfather + {REL_FATHER, REL_MOTHER, REL_GRANDMOTHER}, // Father's mother is grandmother + {REL_MOTHER, REL_FATHER, REL_GRANDFATHER}, // Mother's father is grandfather + {REL_MOTHER, REL_MOTHER, REL_GRANDMOTHER}, // Mother's mother is grandmother + {REL_GRANDFATHER, REL_SON, REL_FATHER}, // Grandfather to son is father + {REL_GRANDFATHER, REL_DAUGHTER, REL_AUNT}, // Grandfather to daughter is aunt + {REL_GRANDMOTHER, REL_SON, REL_FATHER}, // Grandmother to son is father + {REL_GRANDMOTHER, REL_DAUGHTER, REL_AUNT}, // Grandmother to daughter is aunt + + // Great - grandparent relationships + {REL_GRANDFATHER, REL_FATHER, REL_GREAT_GRANDFATHER}, // Grandfather's father is great - grandfather + {REL_GRANDFATHER, REL_MOTHER, REL_GREAT_GRANDMOTHER}, // Grandfather's mother is great - grandmother + {REL_GRANDMOTHER, REL_FATHER, REL_GREAT_GRANDFATHER}, // Grandmother's father is great - grandfather + {REL_GRANDMOTHER, REL_MOTHER, REL_GREAT_GRANDMOTHER}, // Grandmother's mother is great - grandmother + {REL_MATERNAL_GRANDFATHER, REL_FATHER, REL_MATERNAL_GREAT_GRANDFATHER}, // Maternal grandfather's father is maternal great - grandfather + {REL_MATERNAL_GRANDFATHER, REL_MOTHER, REL_MATERNAL_GREAT_GRANDMOTHER}, // Maternal grandfather's mother is maternal great - grandmother + {REL_MATERNAL_GRANDMOTHER, REL_FATHER, REL_MATERNAL_GREAT_GRANDFATHER}, // Maternal grandmother's father is maternal great - grandfather + {REL_MATERNAL_GRANDMOTHER, REL_MOTHER, REL_MATERNAL_GREAT_GRANDMOTHER}, // Maternal grandmother's mother is maternal great - grandmother + + // Sibling relationships + {REL_ELDER_BROTHER, REL_FATHER, REL_FATHER}, + {REL_ELDER_BROTHER, REL_MOTHER, REL_MOTHER}, + {REL_YOUNGER_BROTHER, REL_FATHER, REL_FATHER}, // Younger brother to father remains father + {REL_YOUNGER_BROTHER, REL_MOTHER, REL_MOTHER}, // Younger brother to mother remains younger brother + {REL_ELDER_SISTER, REL_FATHER, REL_ELDER_SISTER}, // Elder sister to father remains elder sister + {REL_ELDER_SISTER, REL_MOTHER, REL_ELDER_SISTER}, // Elder sister to mother remains elder sister + {REL_YOUNGER_SISTER, REL_FATHER, REL_YOUNGER_SISTER}, // Younger sister to father remains younger sister + {REL_YOUNGER_SISTER, REL_MOTHER, REL_YOUNGER_SISTER}, // Younger sister to mother remains younger sister + {REL_ELDER_BROTHER, REL_ELDER_BROTHER, REL_ELDER_BROTHER}, // Elder brother to elder brother remains elder brother + {REL_ELDER_BROTHER, REL_YOUNGER_BROTHER, REL_YOUNGER_BROTHER}, // Elder brother to younger brother remains elder brother + {REL_YOUNGER_BROTHER, REL_ELDER_BROTHER, REL_ELDER_BROTHER}, // Younger brother to elder brother remains younger brother + {REL_YOUNGER_BROTHER, REL_YOUNGER_BROTHER, REL_YOUNGER_BROTHER}, // Younger brother to younger brother remains younger brother + {REL_ELDER_SISTER, REL_ELDER_SISTER, REL_ELDER_SISTER}, // Elder sister to elder sister remains elder sister + {REL_ELDER_SISTER, REL_YOUNGER_SISTER, REL_ELDER_SISTER}, // Elder sister to younger sister remains elder sister + {REL_YOUNGER_SISTER, REL_ELDER_SISTER, REL_YOUNGER_SISTER}, // Younger sister to elder sister remains younger sister + {REL_YOUNGER_SISTER, REL_YOUNGER_SISTER, REL_YOUNGER_SISTER}, // Younger sister to younger sister remains younger sister + + // Spouse relationships + {REL_WIFE, REL_HUSBAND, REL_SELF}, + {REL_HUSBAND, REL_WIFE, REL_SELF}, + + // Uncle and aunt relationships + {REL_FATHER, REL_ELDER_BROTHER, REL_UNCLE}, // Father's elder brother is uncle + {REL_FATHER, REL_YOUNGER_BROTHER, REL_UNCLE}, // Father's younger brother is uncle + {REL_FATHER, REL_ELDER_SISTER, REL_AUNT}, // Father's elder sister is aunt + {REL_FATHER, REL_YOUNGER_SISTER, REL_AUNT}, // Father's younger sister is aunt + {REL_MOTHER, REL_ELDER_BROTHER, REL_MATERNAL_UNCLE}, // Mother's elder brother is maternal uncle + {REL_MOTHER, REL_YOUNGER_BROTHER, REL_MATERNAL_UNCLE}, // Mother's younger brother is maternal uncle + {REL_MOTHER, REL_ELDER_SISTER, REL_MATERNAL_AUNT}, // Mother's elder sister is maternal aunt + {REL_MOTHER, REL_YOUNGER_SISTER, REL_MATERNAL_AUNT}, // Mother's younger sister is maternal aunt + {REL_UNCLE, REL_SON, REL_COUSIN_MALE}, // Uncle to son is male cousin + {REL_UNCLE, REL_DAUGHTER, REL_COUSIN_FEMALE}, // Uncle to daughter is female cousin + {REL_AUNT, REL_SON, REL_COUSIN_MALE}, // Aunt to son is male cousin + {REL_AUNT, REL_DAUGHTER, REL_COUSIN_FEMALE}, // Aunt to daughter is female cousin + {REL_MATERNAL_UNCLE, REL_SON, REL_COUSIN_MALE}, // Maternal uncle to son is male cousin + {REL_MATERNAL_UNCLE, REL_DAUGHTER, REL_COUSIN_FEMALE}, // Maternal uncle to daughter is female cousin + {REL_MATERNAL_AUNT, REL_SON, REL_COUSIN_MALE}, // Maternal aunt to son is male cousin + {REL_MATERNAL_AUNT, REL_DAUGHTER, REL_COUSIN_FEMALE}, // Maternal aunt to daughter is female cousin + + // Cousin relationships + {REL_COUSIN_MALE, REL_FATHER, REL_COUSIN_MALE}, // Male cousin to father remains male cousin + {REL_COUSIN_MALE, REL_MOTHER, REL_COUSIN_MALE}, // Male cousin to mother remains male cousin + {REL_COUSIN_FEMALE, REL_FATHER, REL_COUSIN_FEMALE}, // Female cousin to father remains female cousin + {REL_COUSIN_FEMALE, REL_MOTHER, REL_COUSIN_FEMALE}, // Female cousin to mother remains female cousin + + // Great - uncle and great - aunt relationships + {REL_GRANDFATHER, REL_ELDER_BROTHER, REL_GREAT_UNCLE}, // Grandfather's elder brother is great - uncle + {REL_GRANDFATHER, REL_YOUNGER_BROTHER, REL_GREAT_UNCLE}, // Grandfather's younger brother is great - uncle + {REL_GRANDFATHER, REL_ELDER_SISTER, REL_GREAT_AUNT}, // Grandfather's elder sister is great - aunt + {REL_GRANDFATHER, REL_YOUNGER_SISTER, REL_GREAT_AUNT}, // Grandfather's younger sister is great - aunt + {REL_GREAT_UNCLE, REL_SON, REL_FIRST_COUSIN_ONCE_REMOVED_MALE}, // Great - uncle to son is male first cousin once removed + {REL_GREAT_UNCLE, REL_DAUGHTER, REL_FIRST_COUSIN_ONCE_REMOVED_FEMALE}, // Great - uncle to daughter is female first cousin once removed + {REL_GREAT_AUNT, REL_SON, REL_FIRST_COUSIN_ONCE_REMOVED_MALE}, // Great - aunt to son is male first cousin once removed + {REL_GREAT_AUNT, REL_DAUGHTER, REL_FIRST_COUSIN_ONCE_REMOVED_FEMALE}, // Great - aunt to daughter is female first cousin once removed + + // Second - cousin relationships + {REL_FIRST_COUSIN_ONCE_REMOVED_MALE, REL_SON, REL_SECOND_COUSIN_MALE}, // Male first cousin once removed to son is male second cousin + {REL_FIRST_COUSIN_ONCE_REMOVED_MALE, REL_DAUGHTER, REL_SECOND_COUSIN_FEMALE}, // Male first cousin once removed to daughter is female second cousin + {REL_FIRST_COUSIN_ONCE_REMOVED_FEMALE, REL_SON, REL_SECOND_COUSIN_MALE}, // Female first cousin once removed to son is male second cousin + {REL_FIRST_COUSIN_ONCE_REMOVED_FEMALE, REL_DAUGHTER, REL_SECOND_COUSIN_FEMALE}, // Female first cousin once removed to daughter is female second cousin + + // Nephew and niece relationships + {REL_ELDER_BROTHER, REL_SON, REL_NEPHEW}, // Elder brother to son is nephew + {REL_ELDER_BROTHER, REL_DAUGHTER, REL_NIECE}, // Elder brother to daughter is niece + {REL_YOUNGER_BROTHER, REL_SON, REL_NEPHEW}, // Younger brother to son is nephew + {REL_YOUNGER_BROTHER, REL_DAUGHTER, REL_NIECE}, // Younger brother to daughter is niece + {REL_ELDER_SISTER, REL_SON, REL_NEPHEW}, // Elder sister to son is nephew + {REL_ELDER_SISTER, REL_DAUGHTER, REL_NIECE}, // Elder sister to daughter is niece + {REL_YOUNGER_SISTER, REL_SON, REL_NEPHEW}, // Younger sister to son is nephew + {REL_YOUNGER_SISTER, REL_DAUGHTER, REL_NIECE}, // Younger sister to daughter is niece + + {REL_SELF, REL_SON_IN_LAW, REL_SON_IN_LAW}, // Self to son - in - law is son - in - law + {REL_SELF, REL_DAUGHTER_IN_LAW, REL_DAUGHTER_IN_LAW}, // Self to daughter - in - law is daughter - in - law + {REL_SELF, REL_GRANDSON, REL_GRANDSON}, // Self to grandson is grandson + {REL_SELF, REL_GRANDDAUGHTER, REL_GRANDDAUGHTER}, // Self to granddaughter is granddaughter + {REL_SELF, REL_GREAT_GRANDSON, REL_GREAT_GRANDSON}, // Self to great - grandson is great - grandson + {REL_SELF, REL_GREAT_GRANDDAUGHTER, REL_GREAT_GRANDDAUGHTER}, // Self to great - granddaughter is great - granddaughter + + {REL_SON, REL_SON, REL_GRANDSON}, // Son to son is grandson + {REL_SON, REL_DAUGHTER, REL_GRANDDAUGHTER}, // Son to daughter is granddaughter + {REL_DAUGHTER, REL_SON, REL_GRANDSON}, // Daughter to son is grandson + {REL_DAUGHTER, REL_DAUGHTER, REL_GRANDDAUGHTER}, // Daughter to daughter is granddaughter + + {REL_GRANDSON, REL_FATHER, REL_SON}, // Grandson to father is son + {REL_GRANDSON, REL_MOTHER, REL_DAUGHTER_IN_LAW}, // Grandson to mother is daughter + {REL_GRANDDAUGHTER, REL_FATHER, REL_SON}, // Granddaughter to father is son + {REL_GRANDDAUGHTER, REL_MOTHER, REL_DAUGHTER_IN_LAW}, // Granddaughter to mother is daughter + + {REL_SON_IN_LAW, REL_HUSBAND, REL_SON}, // Son - in - law to husband is son + {REL_DAUGHTER_IN_LAW, REL_WIFE, REL_DAUGHTER}, // Daughter - in - law to wife is daughter + + {REL_GRANDSON, REL_SON, REL_GREAT_GRANDSON}, // Grandson to son is great - grandson + {REL_GRANDDAUGHTER, REL_DAUGHTER, REL_GREAT_GRANDDAUGHTER}, // Granddaughter to daughter is great - granddaughter + + // Newly added other junior - generation relationships + {REL_SON, REL_SON, REL_GRANDSON}, // Son to son is grandson + {REL_SON, REL_DAUGHTER, REL_GRANDDAUGHTER}, // Son to daughter is granddaughter + {REL_DAUGHTER, REL_SON, REL_GRANDSON}, // Daughter to son is grandson + {REL_DAUGHTER, REL_DAUGHTER, REL_GRANDDAUGHTER}, // Daughter to daughter is granddaughter + + {REL_NEPHEW, REL_SON, REL_GRAND_NEPHEW}, // Nephew to son is grand - nephew + {REL_NEPHEW, REL_DAUGHTER, REL_GRAND_NIECE}, // Nephew to daughter is grand - niece + {REL_NIECE, REL_SON, REL_GRAND_NEPHEW}, // Niece to son is grand - nephew + {REL_NIECE, REL_DAUGHTER, REL_GRAND_NIECE}, // Niece to daughter is grand - niece + + {REL_COUSIN_MALE, REL_SON, REL_COUSIN_SON}, // Male cousin to son is cousin - son + {REL_COUSIN_MALE, REL_DAUGHTER, REL_COUSIN_DAUGHTER}, // Male cousin to daughter is cousin - daughter + {REL_COUSIN_FEMALE, REL_SON, REL_COUSIN_SON}, // Female cousin to son is cousin - son + {REL_COUSIN_FEMALE, REL_DAUGHTER, REL_COUSIN_DAUGHTER}, // Female cousin to daughter is cousin - daughter + + {REL_SON, REL_WIFE, REL_SON_IN_LAW}, // Son to wife is son - in - law + {REL_DAUGHTER, REL_HUSBAND, REL_DAUGHTER_IN_LAW}, // Daughter to husband is daughter - in - law + + {REL_SON_IN_LAW, REL_FATHER, REL_SON}, // Son - in - law to father is son + {REL_DAUGHTER_IN_LAW, REL_FATHER, REL_DAUGHTER}, // Daughter - in - law to father is daughter + + {REL_NULL, REL_NULL, REL_NULL} // End marker +}; + +static const char *relation_names[] = { + "自己", // REL_SELF + "父亲", // REL_FATHER + "母亲", // REL_MOTHER + "儿子", // REL_SON + "女儿", // REL_DAUGHTER + "妻子", // REL_WIFE + "丈夫", // REL_HUSBAND + "哥哥", // REL_ELDER_BROTHER + "弟弟", // REL_YOUNGER_BROTHER + "姐姐", // REL_ELDER_SISTER + "妹妹", // REL_YOUNGER_SISTER + "祖父", // REL_GRANDFATHER + "祖母", // REL_GRANDMOTHER + "伯父", // REL_UNCLE + "姑母", // REL_AUNT + "堂兄弟", // REL_COUSIN_MALE + "堂姐妹", // REL_COUSIN_FEMALE + "曾祖父", // REL_GREAT_GRANDFATHER + "曾祖母", // REL_GREAT_GRANDMOTHER + "外祖父", // REL_MATERNAL_GRANDFATHER + "外祖母", // REL_MATERNAL_GRANDMOTHER + "外曾祖父", // REL_MATERNAL_GREAT_GRANDFATHER + "外曾祖母", // REL_MATERNAL_GREAT_GRANDMOTHER + "表侄", // REL_FIRST_COUSIN_ONCE_REMOVED_MALE + "表侄女", // REL_FIRST_COUSIN_ONCE_REMOVED_FEMALE + "再从兄弟", // REL_SECOND_COUSIN_MALE + "再从姐妹", // REL_SECOND_COUSIN_FEMALE + "叔祖父", // REL_GREAT_UNCLE + "姑祖母", // REL_GREAT_AUNT + "舅父", // REL_MATERNAL_UNCLE + "姨母", // REL_MATERNAL_AUNT + "侄子", // REL_NEPHEW + "侄女", // REL_NIECE + "儿媳", // REL_SON_IN_LAW + "女婿", // REL_DAUGHTER_IN_LAW + "孙子", // REL_GRANDSON + "孙女", // REL_GRANDDAUGHTER + "曾孙子", // REL_GREAT_GRANDSON + "曾孙女", // REL_GREAT_GRANDDAUGHTER + "堂侄/表侄", // REL_COUSIN_SON + "堂侄女/表侄女", // REL_COUSIN_DAUGHTER + "孙侄", // REL_GRAND_NEPHEW + "孙侄女", // REL_GRAND_NIECE + "未知" // REL_NULL +}; + +static const btnm_relation_t btnm_relation[] = { + {"父亲", REL_FATHER, GENDER_MALE}, + {"母亲", REL_MOTHER, GENDER_FEMALE}, + {"丈夫", REL_HUSBAND, GENDER_MALE}, + {"妻子", REL_WIFE, GENDER_FEMALE}, + {"儿子", REL_SON, GENDER_MALE}, + {"女儿", REL_DAUGHTER, GENDER_FEMALE}, + {"哥哥", REL_ELDER_BROTHER, GENDER_MALE}, + {"弟弟", REL_YOUNGER_BROTHER, GENDER_MALE}, + {"姐姐", REL_ELDER_SISTER, GENDER_FEMALE}, + {"妹妹", REL_YOUNGER_SISTER, GENDER_FEMALE} +}; + +static const char *btnm_map[] = { + "父亲", "丈夫", "儿子", "哥哥", "姐姐", "<-", "清除", "\n", + "母亲", "妻子", "女儿", "弟弟", "妹妹", " ", "计算", "" +}; + +static void rel_cal_main_page(void); +static void rel_cal_btnmatrix_event_cb(lv_event_t *e); +static void rel_transform_handle(const uint8_t id, const char *text); +static const char *calculate_relationship(relation_cal_t *self); + +void relation_cal_app_create(void) +{ +#ifdef CONFIG_ESP32S3_BOX_LCD + LV_FONT_DECLARE(lv_font_siyuan_10); + + g_rel_cal.fonts.siyuan = &lv_font_siyuan_10; +#else + LV_FONT_DECLARE(lv_font_siyuan_16); + + g_rel_cal.fonts.siyuan = &lv_font_siyuan_16; +#endif + + if (NULL == g_rel_cal.fonts.siyuan) + { + return; + } + + g_rel_cal.rel_count = 0; + rel_cal_main_page(); +} + +static void rel_cal_main_page(void) +{ + lv_obj_t *root = lv_obj_create(lv_scr_act()); + lv_obj_remove_style_all(root); + lv_obj_center(root); + lv_obj_set_style_bg_color(root, lv_color_white(), 0); + lv_obj_align(root, LV_ALIGN_CENTER, 0, 0); + lv_obj_set_size(root, DEMO_WIDTH, DEMO_HEIGHT); + lv_obj_set_flex_flow(root, LV_FLEX_FLOW_COLUMN); + lv_obj_set_scrollbar_mode(root, LV_SCROLLBAR_MODE_OFF); + lv_obj_clear_flag(root, LV_OBJ_FLAG_SCROLLABLE); + + g_rel_cal.ui.title = lv_label_create(root); + lv_obj_set_size(g_rel_cal.ui.title, DEMO_WIDTH, DEMO_TITLE_HEIGHT); + lv_label_set_text(g_rel_cal.ui.title, "亲属关系计算器"); + lv_obj_set_style_text_font(g_rel_cal.ui.title, g_rel_cal.fonts.siyuan, LV_PART_MAIN); + lv_obj_set_style_text_color(g_rel_cal.ui.title, lv_color_hex(0x2C3E50), 0); + lv_obj_set_style_text_align(g_rel_cal.ui.title, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_pad_top(g_rel_cal.ui.title, 10, 0); + lv_obj_set_style_text_color(g_rel_cal.ui.title, lv_color_hex(0xFFA500), 0); + + g_rel_cal.ui.screen = lv_textarea_create(root); + lv_obj_set_size(g_rel_cal.ui.screen, LV_PCT(100), LV_PCT(40)); + + lv_obj_set_style_text_font(g_rel_cal.ui.screen, g_rel_cal.fonts.siyuan, LV_PART_MAIN); + + lv_obj_set_style_bg_color(g_rel_cal.ui.screen, lv_color_hex(0x2C3E50), 0); + lv_obj_set_style_text_color(g_rel_cal.ui.screen, lv_color_white(), 0); + lv_obj_set_style_radius(g_rel_cal.ui.screen, 20, 0); + lv_obj_set_style_pad_all(g_rel_cal.ui.screen, 15, 0); + lv_textarea_set_cursor_click_pos(g_rel_cal.ui.screen, false); + lv_textarea_set_max_length(g_rel_cal.ui.screen, 128); + lv_textarea_set_align(g_rel_cal.ui.screen, LV_TEXT_ALIGN_LEFT); + lv_textarea_set_text(g_rel_cal.ui.screen, ""); + + g_rel_cal.ui.btnm = lv_btnmatrix_create(root); + lv_obj_set_style_border_width(g_rel_cal.ui.btnm, 0, 0); + lv_obj_set_size(g_rel_cal.ui.btnm, LV_PCT(100), LV_PCT(40)); + + lv_obj_set_style_text_font(g_rel_cal.ui.btnm, g_rel_cal.fonts.siyuan, LV_PART_MAIN); + + lv_buttonmatrix_set_map(g_rel_cal.ui.btnm, btnm_map); + lv_obj_add_event_cb(g_rel_cal.ui.btnm, rel_cal_btnmatrix_event_cb, LV_EVENT_VALUE_CHANGED, root); + lv_obj_set_style_bg_color(g_rel_cal.ui.btnm, lv_color_hex(0xECF0F1), 0); + lv_obj_set_style_radius(g_rel_cal.ui.btnm, 10, 0); + lv_obj_set_style_pad_all(g_rel_cal.ui.btnm, 10, 0); + + g_rel_cal.ui.note = lv_label_create(root); + lv_obj_set_size(g_rel_cal.ui.note, LV_PCT(100), LV_PCT(10)); + lv_label_set_text(g_rel_cal.ui.note, "这是一个亲属关系计算器。未支持关系:“未知”。"); + + lv_obj_set_style_text_font(g_rel_cal.ui.note, g_rel_cal.fonts.siyuan, LV_PART_MAIN); + + lv_obj_set_style_text_color(g_rel_cal.ui.note, lv_color_hex(0x7F8C8D), 0); + lv_obj_set_style_text_align(g_rel_cal.ui.note, LV_TEXT_ALIGN_CENTER, 0); +} + +static void rel_cal_btnmatrix_event_cb(lv_event_t *e) +{ + lv_obj_t *obj = lv_event_get_target(e); + uint8_t id = lv_buttonmatrix_get_selected_button(obj); + const char *text = lv_buttonmatrix_get_button_text(obj, id); + + rel_transform_handle(id, text); +} + +static void rel_transform_handle(const uint8_t id, const char *text) +{ + static btnm_relation_t last_input = {"?", REL_NULL, GENDER_UNKNOWN}; + + if (g_rel_cal.status == STATUS_CALCULATED) + { + g_rel_cal.status = STATUS_INPUTTING; + lv_textarea_set_text(g_rel_cal.ui.screen, ""); + } + + switch (id) + { + case CALCULATED: + if (g_rel_cal.rel_count > 0) + { + const char *result = calculate_relationship(&g_rel_cal); + lv_textarea_set_text(g_rel_cal.ui.screen, result); + g_rel_cal.rel_count = 0; + } + else + { + lv_textarea_set_text(g_rel_cal.ui.screen, "自己"); + g_rel_cal.rel_count = 0; + } + g_rel_cal.status = STATUS_CALCULATED; + last_input.relation = REL_NULL; + last_input.gender = GENDER_UNKNOWN; + + break; + + case CLEAR: + lv_textarea_set_text(g_rel_cal.ui.screen, ""); + g_rel_cal.rel_count = 0; + + last_input.relation = REL_NULL; + last_input.gender = GENDER_UNKNOWN; + break; + + case DELETE: + if (g_rel_cal.rel_count) + { + g_rel_cal.rel_count--; + lv_textarea_delete_char(g_rel_cal.ui.screen); + lv_textarea_delete_char(g_rel_cal.ui.screen); + lv_textarea_delete_char(g_rel_cal.ui.screen); + } + break; + + case MY_NULL: + break; + + default: + for (int i = 0; i < sizeof(btnm_relation) / sizeof(btnm_relation[0]); i++) + { + if (0 == strcmp(text, btnm_relation[i].btnm_text)) + { + if ((GENDER_FEMALE == last_input.gender && + REL_WIFE == btnm_relation[i].relation) || + (GENDER_MALE == last_input.gender && + REL_HUSBAND == btnm_relation[i].relation)) + { + return; + } + if (g_rel_cal.rel_count < MAX_REL_LEN) + { + g_rel_cal.rel_list[g_rel_cal.rel_count++] = btnm_relation[i].relation; + } + + last_input = btnm_relation[i]; + break; + } + } + + if (g_rel_cal.rel_count == 1) + { + lv_textarea_add_text(g_rel_cal.ui.screen, text); + } + else if (g_rel_cal.rel_count > 1) + { + lv_textarea_add_text(g_rel_cal.ui.screen, "的"); + lv_textarea_add_text(g_rel_cal.ui.screen, text); + } + + break; + } +} + +static const char *calculate_relationship(relation_cal_t *self) +{ + relation_type_t current = REL_SELF; + for (int i = 0; i < self->rel_count; i++) + { + int found = 0; + for (int j = 0; transitions[j].from != REL_NULL; j++) + { + if (transitions[j].from == current && transitions[j].to == self->rel_list[i]) + { + current = transitions[j].result; + found = 1; + break; + } + } + if (!found) + { + current = REL_NULL; + break; + } + } + + return relation_names[current]; +} diff --git a/relation_calculator/relation_cal.h b/relation_calculator/relation_cal.h new file mode 100644 index 0000000000000000000000000000000000000000..198da955a844e2ef198a82b076a4c9ffb8b4e3a5 --- /dev/null +++ b/relation_calculator/relation_cal.h @@ -0,0 +1,135 @@ +#ifndef __RELATION_CAL_H__ +#define __RELATION_CAL_H__ + +/********************* + * INCLUDES + *********************/ +#include <lvgl/lvgl.h> + +#define MAX_REL_LEN 256 + +#define FATHER 0 +#define HUSBAND 1 +#define SON 2 +#define ELDER_BRO 3 +#define ELDER_SIS 4 +#define DELETE 5 +#define CLEAR 6 +#define MOTHER 7 +#define WIFI 8 +#define DAUGHTER 9 +#define YOUNGER_BRO 10 +#define YOUNGER_SIS 11 +#define MY_NULL 12 +#define CALCULATED 13 + +#define REL_CAL_RES_ROOT CONFIG_LVX_REL_CAL_DATA_ROOT "/res" +#define REL_CAL_FONTS_ROOT REL_CAL_RES_ROOT "/fonts" + +typedef enum relation_cal_status_e +{ + STATUS_INIT, + STATUS_INPUTTING, + STATUS_CALCULATED, + STATUS_XXX +} relation_cal_status_t; + +typedef enum gender_e +{ + GENDER_MALE, + GENDER_FEMALE, + GENDER_UNKNOWN +} gender_t; + +typedef enum relation_type_e +{ + REL_SELF, // Self + REL_FATHER, // Father + REL_MOTHER, // Mother + REL_SON, // Son + REL_DAUGHTER, // Daughter + REL_WIFE, // Wife + REL_HUSBAND, // Husband + REL_ELDER_BROTHER, // Elder brother + REL_YOUNGER_BROTHER, // Younger brother + REL_ELDER_SISTER, // Elder sister + REL_YOUNGER_SISTER, // Younger sister + REL_GRANDFATHER, // Paternal grandfather + REL_GRANDMOTHER, // Paternal grandmother + REL_UNCLE, // Uncle (father's elder brother) + REL_AUNT, // Aunt (father's sister) + REL_COUSIN_MALE, // Paternal male cousin + REL_COUSIN_FEMALE, // Paternal female cousin + REL_GREAT_GRANDFATHER, // Paternal great - grandfather + REL_GREAT_GRANDMOTHER, // Paternal great - grandmother + REL_MATERNAL_GRANDFATHER, // Maternal grandfather + REL_MATERNAL_GRANDMOTHER, // Maternal grandmother + REL_MATERNAL_GREAT_GRANDFATHER, // Maternal great - grandfather + REL_MATERNAL_GREAT_GRANDMOTHER, // Maternal great - grandmother + REL_FIRST_COUSIN_ONCE_REMOVED_MALE, // First cousin once removed (male) + REL_FIRST_COUSIN_ONCE_REMOVED_FEMALE, // First cousin once removed (female) + REL_SECOND_COUSIN_MALE, // Second male cousin + REL_SECOND_COUSIN_FEMALE, // Second female cousin + REL_GREAT_UNCLE, // Granduncle (grandfather's younger brother) + REL_GREAT_AUNT, // Grandaunt (grandfather's sister) + REL_MATERNAL_UNCLE, // Maternal uncle (mother's brother) + REL_MATERNAL_AUNT, // Maternal aunt (mother's sister) + REL_NEPHEW, // Nephew + REL_NIECE, // Niece + REL_SON_IN_LAW, // Son - in - law + REL_DAUGHTER_IN_LAW, // Daughter - in - law + REL_GRANDSON, // Grandson + REL_GRANDDAUGHTER, // Granddaughter + REL_GREAT_GRANDSON, // Great - grandson + REL_GREAT_GRANDDAUGHTER, // Great - granddaughter + REL_COUSIN_SON, // Cousin's son + REL_COUSIN_DAUGHTER, // Cousin's daughter + REL_GRAND_NEPHEW, // Grand - nephew (brother's grandson) + REL_GRAND_NIECE, // Grand - niece (brother's granddaughter) + REL_NULL // Unknown +} relation_type_t; + +typedef struct btnm_relation_s +{ + const char *btnm_text; + relation_type_t relation; + gender_t gender; +} btnm_relation_t; + +typedef struct relation_call_s +{ + const char *call; + relation_type_t relation; +} relation_call_t; + +typedef struct relation_cal_s +{ + struct + { + lv_obj_t *title; + lv_obj_t *note; + lv_obj_t *btnm; + lv_obj_t *screen; + } ui; + + struct + { + const lv_font_t *siyuan; + } fonts; + + relation_type_t rel_list[MAX_REL_LEN]; + int rel_count; + relation_cal_status_t status; + +} relation_cal_t; + +typedef struct relation_transformation_s +{ + relation_type_t from; + relation_type_t to; + relation_type_t result; +} relation_transformation_t; + +void relation_cal_app_create(void); + +#endif /* __RELATION_CAL_H__ */ diff --git a/relation_calculator/relation_cal_main.c b/relation_calculator/relation_cal_main.c new file mode 100644 index 0000000000000000000000000000000000000000..d785930bcfebcf5245c40e49230f90fb61c54a0b --- /dev/null +++ b/relation_calculator/relation_cal_main.c @@ -0,0 +1,93 @@ +// include NuttX headers +#include <nuttx/config.h> +#include <unistd.h> + +#ifdef CONFIG_LV_USE_NUTTX_LIBUV +#include <uv.h> +#endif + +// include lvgl headers +#include <lvgl/lvgl.h> + +#include "relation_cal.h" + +#ifdef CONFIG_LV_USE_NUTTX_LIBUV + +static void lv_nuttx_uv_loop(uv_loop_t *loop, lv_nuttx_result_t *result) +{ + lv_nuttx_uv_t uv_info; + void *data; + + uv_loop_init(loop); + + lv_memset(&uv_info, 0, sizeof(uv_info)); + uv_info.loop = loop; + uv_info.disp = result->disp; + uv_info.indev = result->indev; +#ifdef CONFIG_UINPUT_TOUCH + uv_info.uindev = result->utouch_indev; +#endif + + data = lv_nuttx_uv_init(&uv_info); + uv_run(loop, UV_RUN_DEFAULT); + lv_nuttx_uv_deinit(&data); +} + +#endif + +int main(int argc, FAR char *argv[]) +{ + // init lvgl + lv_nuttx_dsc_t info; + lv_nuttx_result_t result; + +#ifdef CONFIG_LV_USE_NUTTX_LIBUV + uv_loop_t ui_loop; + lv_memset(&ui_loop, 0, sizeof(uv_loop_t)); +#endif + + if (lv_is_initialized()) + { + LV_LOG_ERROR("LVGL already initialized! aborting."); + return -1; + } + + lv_init(); + + lv_nuttx_dsc_init(&info); + +#ifdef CONFIG_LV_USE_NUTTX_LCD + info.fb_path = "/dev/lcd0"; +#endif + + lv_nuttx_init(&info, &result); + + if (result.disp == NULL) + { + LV_LOG_ERROR("lv_demos initialization failure!"); + return 1; + } + + relation_cal_app_create(); + +#ifdef CONFIG_LV_USE_NUTTX_LIBUV + // refresh lvgl ui + lv_nuttx_uv_loop(&ui_loop, &result); +#endif + + while (1) + { + uint32_t idle; + idle = lv_timer_handler(); + + /* Minimum sleep of 1ms */ + + idle = idle ? idle : 1; + usleep(idle * 1000); + } + + lv_nuttx_deinit(&result); + lv_deinit(); + + return 0; +} diff --git a/x_track/Kconfig b/x_track/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..1f176f173f874549e1c8a472a0b88c2f45dbb3df --- /dev/null +++ b/x_track/Kconfig @@ -0,0 +1,19 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config LVX_USE_DEMO_X_TRACK + bool "X-TRACK demo" + depends on GRAPHICS_LVGL && LV_USE_LIBPNG && LIB_PNG && NETUTILS_CJSON && UIKIT_FONT_MANAGER + default n + ---help--- + Enable build X-TRACK Demo programs + +if LVX_USE_DEMO_X_TRACK + +config X_TRACK_RESOURCE_DIR_PATH + string "Resource directory path for X-TRACK" + default "/data" + +endif # LVX_USE_DEMO_X_TRACK diff --git a/x_track/Make.defs b/x_track/Make.defs new file mode 100644 index 0000000000000000000000000000000000000000..75149bb8d23228aa2e0b98bfa2da0a59e9f467f6 --- /dev/null +++ b/x_track/Make.defs @@ -0,0 +1,18 @@ +# +# Copyright (C) 2024 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +ifneq ($(CONFIG_LVX_USE_DEMO_X_TRACK),) +CONFIGURED_APPS += $(APPDIR)/packages/demos/x_track +endif diff --git a/x_track/Makefile b/x_track/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..48c1b4ab3a1a6afc70ea938d16fcd088ec6f8979 --- /dev/null +++ b/x_track/Makefile @@ -0,0 +1,33 @@ +include $(APPDIR)/Make.defs + +ifeq ($(CONFIG_LVX_USE_DEMO_X_TRACK), y) + +CXXEXT := .cpp + +PROGNAME += x_track +PRIORITY = 100 +STACKSIZE = 32768 +MODULE = $(CONFIG_LVX_USE_DEMO_X_TRACK) + +# Absolute page size mode +CXXFLAGS += -DPAGE_HOR_RES=240 -DPAGE_VER_RES=320 + +# Map resource path configuration +CXXFLAGS += -DCONFIG_RESOURCE_DIR_PATH=CONFIG_X_TRACK_RESOURCE_DIR_PATH + +# Log level configuration +CXXFLAGS += -DDATA_BROKER_LOG_LEVEL=1 -DPAGE_LOG_LEVEL=0 + +# Include paths +CXXFLAGS += ${INCDIR_PREFIX}$(APPDIR)/include/netutils +CXXFLAGS += ${INCDIR_PREFIX}$(APPDIR)/frameworks/graphics/uikit/include +CXXFLAGS += ${INCDIR_PREFIX}$(APPDIR)/packages/demos/x_track/src +CXXFLAGS += ${INCDIR_PREFIX}$(APPDIR)/packages/demos/x_track/src/App +CXXFLAGS += ${INCDIR_PREFIX}$(APPDIR)/packages/demos/x_track/src/Vendor/Simulator + +MAINSRC += x_track_main.cpp +CSRCS += $(shell find -L ./ -name "*.c") +CXXSRCS += $(shell find -L ./ -name "*.cpp" | grep -v x_track_main.cpp) +endif # CONFIG_LVX_USE_DEMO_X_TRACK + +include $(APPDIR)/Application.mk diff --git a/x_track/resource/font/Alibaba_PuHuiTi_2.0_115_Black.ttf b/x_track/resource/font/Alibaba_PuHuiTi_2.0_115_Black.ttf new file mode 100755 index 0000000000000000000000000000000000000000..70281c0ce65ad3067aa4585c8211d2f83c497549 Binary files /dev/null and b/x_track/resource/font/Alibaba_PuHuiTi_2.0_115_Black.ttf differ diff --git a/x_track/resource/font/Alibaba_PuHuiTi_2.0_55_Regular.ttf b/x_track/resource/font/Alibaba_PuHuiTi_2.0_55_Regular.ttf new file mode 100755 index 0000000000000000000000000000000000000000..b69b5109d35e564a0a9fd31b2190e68f796e108d Binary files /dev/null and b/x_track/resource/font/Alibaba_PuHuiTi_2.0_55_Regular.ttf differ diff --git a/x_track/resource/font/Alibaba_PuHuiTi_2.0_65_Medium.ttf b/x_track/resource/font/Alibaba_PuHuiTi_2.0_65_Medium.ttf new file mode 100755 index 0000000000000000000000000000000000000000..588a02dddbd178b7b19606789837ca27ef55202b Binary files /dev/null and b/x_track/resource/font/Alibaba_PuHuiTi_2.0_65_Medium.ttf differ diff --git a/x_track/resource/font/Font Awesome 6 Brands-Regular-400.ttf b/x_track/resource/font/Font Awesome 6 Brands-Regular-400.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d2fc00991f6a8edbbafddc0d3776cf1e7792ee99 Binary files /dev/null and b/x_track/resource/font/Font Awesome 6 Brands-Regular-400.ttf differ diff --git a/x_track/resource/font/Font Awesome 6 Free-Regular-400.ttf b/x_track/resource/font/Font Awesome 6 Free-Regular-400.ttf new file mode 100644 index 0000000000000000000000000000000000000000..59f4a6edd45234250ce78277cfe0c13aabaf211d Binary files /dev/null and b/x_track/resource/font/Font Awesome 6 Free-Regular-400.ttf differ diff --git a/x_track/resource/font/Font Awesome 6 Free-Solid-900.ttf b/x_track/resource/font/Font Awesome 6 Free-Solid-900.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a16a38fb46811bfff03bd3b5db478131ecbf7253 Binary files /dev/null and b/x_track/resource/font/Font Awesome 6 Free-Solid-900.ttf differ diff --git a/x_track/resource/images/navi_arrow_dark.png b/x_track/resource/images/navi_arrow_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..cd02eccbed4834c57cb974fef46996eb926c29e3 Binary files /dev/null and b/x_track/resource/images/navi_arrow_dark.png differ diff --git a/x_track/resource/images/navi_arrow_light.png b/x_track/resource/images/navi_arrow_light.png new file mode 100644 index 0000000000000000000000000000000000000000..ec0d98efd81b3e07ca9c119f9eda79418d356e8e Binary files /dev/null and b/x_track/resource/images/navi_arrow_light.png differ diff --git a/x_track/resource/track/TRK_EXAMPLE.gpx b/x_track/resource/track/TRK_EXAMPLE.gpx new file mode 100644 index 0000000000000000000000000000000000000000..536c5a062f4e9d346fed65c1728860b3f729afe5 --- /dev/null +++ b/x_track/resource/track/TRK_EXAMPLE.gpx @@ -0,0 +1,2751 @@ +<gpx version="1.1" creator="Arduino GPX Lib" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://www.topografix.com/GPX/1/1" + xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" +> +<metadata><name><![CDATA[X-TRACK EVO v1.0.0]]></name> +<desc><![CDATA[https://github.com/X-TRACK-OVERFLOW/X-TRACK-EVO]]></desc> +</metadata> +<trk><name><![CDATA[/Track/2024_11/TRK_20241111_113804.gpx]]></name> +<trkseg><trkpt lat="40.048031" lon="116.309013"><ele>0.00</ele> +<time>2024-11-11T11:38:04Z</time> +</trkpt> +<trkpt lat="40.048069" lon="116.309021"><ele>0.00</ele> +<time>2024-11-11T11:38:04Z</time> +</trkpt> +<trkpt lat="40.048054" lon="116.309021"><ele>0.00</ele> +<time>2024-11-11T11:38:05Z</time> +</trkpt> +<trkpt lat="40.048038" lon="116.309029"><ele>0.00</ele> +<time>2024-11-11T11:38:07Z</time> +</trkpt> +<trkpt lat="40.048008" lon="116.309052"><ele>0.00</ele> +<time>2024-11-11T11:38:08Z</time> +</trkpt> +<trkpt lat="40.047970" lon="116.309074"><ele>0.00</ele> +<time>2024-11-11T11:38:09Z</time> +</trkpt> +<trkpt lat="40.047935" lon="116.309074"><ele>0.00</ele> +<time>2024-11-11T11:38:09Z</time> +</trkpt> +<trkpt lat="40.047874" lon="116.309090"><ele>0.00</ele> +<time>2024-11-11T11:38:10Z</time> +</trkpt> +<trkpt lat="40.047825" lon="116.309090"><ele>0.00</ele> +<time>2024-11-11T11:38:11Z</time> +</trkpt> +<trkpt lat="40.047779" lon="116.309082"><ele>0.00</ele> +<time>2024-11-11T11:38:12Z</time> +</trkpt> +<trkpt lat="40.047745" lon="116.309067"><ele>0.00</ele> +<time>2024-11-11T11:38:13Z</time> +</trkpt> +<trkpt lat="40.047710" lon="116.309044"><ele>0.00</ele> +<time>2024-11-11T11:38:14Z</time> +</trkpt> +<trkpt lat="40.047699" lon="116.309013"><ele>0.00</ele> +<time>2024-11-11T11:38:15Z</time> +</trkpt> +<trkpt lat="40.047665" lon="116.308968"><ele>0.00</ele> +<time>2024-11-11T11:38:16Z</time> +</trkpt> +<trkpt lat="40.047626" lon="116.308922"><ele>0.00</ele> +<time>2024-11-11T11:38:17Z</time> +</trkpt> +<trkpt lat="40.047604" lon="116.308868"><ele>0.00</ele> +<time>2024-11-11T11:38:19Z</time> +</trkpt> +<trkpt lat="40.047550" lon="116.308815"><ele>0.00</ele> +<time>2024-11-11T11:38:20Z</time> +</trkpt> +<trkpt lat="40.047504" lon="116.308754"><ele>0.00</ele> +<time>2024-11-11T11:38:21Z</time> +</trkpt> +<trkpt lat="40.047466" lon="116.308693"><ele>0.00</ele> +<time>2024-11-11T11:38:21Z</time> +</trkpt> +<trkpt lat="40.047413" lon="116.308632"><ele>0.00</ele> +<time>2024-11-11T11:38:22Z</time> +</trkpt> +<trkpt lat="40.047359" lon="116.308571"><ele>0.00</ele> +<time>2024-11-11T11:38:23Z</time> +</trkpt> +<trkpt lat="40.047310" lon="116.308510"><ele>0.00</ele> +<time>2024-11-11T11:38:24Z</time> +</trkpt> +<trkpt lat="40.047272" lon="116.308456"><ele>0.00</ele> +<time>2024-11-11T11:38:25Z</time> +</trkpt> +<trkpt lat="40.047237" lon="116.308395"><ele>0.00</ele> +<time>2024-11-11T11:38:26Z</time> +</trkpt> +<trkpt lat="40.047203" lon="116.308334"><ele>0.00</ele> +<time>2024-11-11T11:38:27Z</time> +</trkpt> +<trkpt lat="40.047176" lon="116.308273"><ele>0.00</ele> +<time>2024-11-11T11:38:28Z</time> +</trkpt> +<trkpt lat="40.047176" lon="116.308273"><ele>0.00</ele> +<time>2024-11-11T11:38:29Z</time> +</trkpt> +<trkpt lat="40.047176" lon="116.308273"><ele>0.00</ele> +<time>2024-11-11T11:38:30Z</time> +</trkpt> +<trkpt lat="40.047089" lon="116.308098"><ele>0.00</ele> +<time>2024-11-11T11:38:32Z</time> +</trkpt> +<trkpt lat="40.047066" lon="116.308037"><ele>0.00</ele> +<time>2024-11-11T11:38:32Z</time> +</trkpt> +<trkpt lat="40.047039" lon="116.307968"><ele>0.00</ele> +<time>2024-11-11T11:38:33Z</time> +</trkpt> +<trkpt lat="40.047016" lon="116.307907"><ele>0.00</ele> +<time>2024-11-11T11:38:34Z</time> +</trkpt> +<trkpt lat="40.046989" lon="116.307838"><ele>0.00</ele> +<time>2024-11-11T11:38:35Z</time> +</trkpt> +<trkpt lat="40.046970" lon="116.307777"><ele>0.00</ele> +<time>2024-11-11T11:38:36Z</time> +</trkpt> +<trkpt lat="40.046947" lon="116.307709"><ele>0.00</ele> +<time>2024-11-11T11:38:37Z</time> +</trkpt> +<trkpt lat="40.046925" lon="116.307648"><ele>0.00</ele> +<time>2024-11-11T11:38:38Z</time> +</trkpt> +<trkpt lat="40.046909" lon="116.307587"><ele>0.00</ele> +<time>2024-11-11T11:38:39Z</time> +</trkpt> +<trkpt lat="40.046883" lon="116.307518"><ele>0.00</ele> +<time>2024-11-11T11:38:40Z</time> +</trkpt> +<trkpt lat="40.046860" lon="116.307457"><ele>0.00</ele> +<time>2024-11-11T11:38:41Z</time> +</trkpt> +<trkpt lat="40.046837" lon="116.307388"><ele>0.00</ele> +<time>2024-11-11T11:38:42Z</time> +</trkpt> +<trkpt lat="40.046818" lon="116.307327"><ele>0.00</ele> +<time>2024-11-11T11:38:43Z</time> +</trkpt> +<trkpt lat="40.046795" lon="116.307259"><ele>0.00</ele> +<time>2024-11-11T11:38:44Z</time> +</trkpt> +<trkpt lat="40.046776" lon="116.307190"><ele>0.00</ele> +<time>2024-11-11T11:38:45Z</time> +</trkpt> +<trkpt lat="40.046749" lon="116.307129"><ele>0.00</ele> +<time>2024-11-11T11:38:46Z</time> +</trkpt> +<trkpt lat="40.046726" lon="116.307060"><ele>0.00</ele> +<time>2024-11-11T11:38:47Z</time> +</trkpt> +<trkpt lat="40.046715" lon="116.306992"><ele>0.00</ele> +<time>2024-11-11T11:38:48Z</time> +</trkpt> +<trkpt lat="40.046696" lon="116.306923"><ele>0.00</ele> +<time>2024-11-11T11:38:49Z</time> +</trkpt> +<trkpt lat="40.046677" lon="116.306854"><ele>0.00</ele> +<time>2024-11-11T11:38:50Z</time> +</trkpt> +<trkpt lat="40.046658" lon="116.306786"><ele>0.00</ele> +<time>2024-11-11T11:38:51Z</time> +</trkpt> +<trkpt lat="40.046642" lon="116.306725"><ele>0.00</ele> +<time>2024-11-11T11:38:52Z</time> +</trkpt> +<trkpt lat="40.046616" lon="116.306664"><ele>0.00</ele> +<time>2024-11-11T11:38:53Z</time> +</trkpt> +<trkpt lat="40.046566" lon="116.306602"><ele>0.00</ele> +<time>2024-11-11T11:38:54Z</time> +</trkpt> +<trkpt lat="40.046516" lon="116.306541"><ele>0.00</ele> +<time>2024-11-11T11:38:55Z</time> +</trkpt> +<trkpt lat="40.046478" lon="116.306480"><ele>0.00</ele> +<time>2024-11-11T11:38:56Z</time> +</trkpt> +<trkpt lat="40.046444" lon="116.306419"><ele>0.00</ele> +<time>2024-11-11T11:38:57Z</time> +</trkpt> +<trkpt lat="40.046417" lon="116.306358"><ele>0.00</ele> +<time>2024-11-11T11:38:58Z</time> +</trkpt> +<trkpt lat="40.046391" lon="116.306290"><ele>0.00</ele> +<time>2024-11-11T11:38:59Z</time> +</trkpt> +<trkpt lat="40.046364" lon="116.306229"><ele>0.00</ele> +<time>2024-11-11T11:39:00Z</time> +</trkpt> +<trkpt lat="40.046341" lon="116.306168"><ele>0.00</ele> +<time>2024-11-11T11:39:01Z</time> +</trkpt> +<trkpt lat="40.046318" lon="116.306107"><ele>0.00</ele> +<time>2024-11-11T11:39:02Z</time> +</trkpt> +<trkpt lat="40.046291" lon="116.306046"><ele>0.00</ele> +<time>2024-11-11T11:39:03Z</time> +</trkpt> +<trkpt lat="40.046272" lon="116.305984"><ele>0.00</ele> +<time>2024-11-11T11:39:04Z</time> +</trkpt> +<trkpt lat="40.046249" lon="116.305916"><ele>0.00</ele> +<time>2024-11-11T11:39:05Z</time> +</trkpt> +<trkpt lat="40.046230" lon="116.305855"><ele>0.00</ele> +<time>2024-11-11T11:39:06Z</time> +</trkpt> +<trkpt lat="40.046207" lon="116.305794"><ele>0.00</ele> +<time>2024-11-11T11:39:07Z</time> +</trkpt> +<trkpt lat="40.046185" lon="116.305733"><ele>0.00</ele> +<time>2024-11-11T11:39:08Z</time> +</trkpt> +<trkpt lat="40.046162" lon="116.305672"><ele>0.00</ele> +<time>2024-11-11T11:39:09Z</time> +</trkpt> +<trkpt lat="40.046139" lon="116.305611"><ele>0.00</ele> +<time>2024-11-11T11:39:10Z</time> +</trkpt> +<trkpt lat="40.046112" lon="116.305550"><ele>0.00</ele> +<time>2024-11-11T11:39:11Z</time> +</trkpt> +<trkpt lat="40.046082" lon="116.305496"><ele>0.00</ele> +<time>2024-11-11T11:39:12Z</time> +</trkpt> +<trkpt lat="40.046043" lon="116.305450"><ele>0.00</ele> +<time>2024-11-11T11:39:13Z</time> +</trkpt> +<trkpt lat="40.045998" lon="116.305420"><ele>0.00</ele> +<time>2024-11-11T11:39:14Z</time> +</trkpt> +<trkpt lat="40.045956" lon="116.305397"><ele>0.00</ele> +<time>2024-11-11T11:39:15Z</time> +</trkpt> +<trkpt lat="40.045910" lon="116.305389"><ele>0.00</ele> +<time>2024-11-11T11:39:16Z</time> +</trkpt> +<trkpt lat="40.045860" lon="116.305397"><ele>0.00</ele> +<time>2024-11-11T11:39:17Z</time> +</trkpt> +<trkpt lat="40.045815" lon="116.305420"><ele>0.00</ele> +<time>2024-11-11T11:39:18Z</time> +</trkpt> +<trkpt lat="40.045765" lon="116.305450"><ele>0.00</ele> +<time>2024-11-11T11:39:19Z</time> +</trkpt> +<trkpt lat="40.045715" lon="116.305481"><ele>0.00</ele> +<time>2024-11-11T11:39:20Z</time> +</trkpt> +<trkpt lat="40.045666" lon="116.305511"><ele>0.00</ele> +<time>2024-11-11T11:39:21Z</time> +</trkpt> +<trkpt lat="40.045620" lon="116.305542"><ele>0.00</ele> +<time>2024-11-11T11:39:22Z</time> +</trkpt> +<trkpt lat="40.045574" lon="116.305573"><ele>0.00</ele> +<time>2024-11-11T11:39:23Z</time> +</trkpt> +<trkpt lat="40.045528" lon="116.305603"><ele>0.00</ele> +<time>2024-11-11T11:39:24Z</time> +</trkpt> +<trkpt lat="40.045486" lon="116.305641"><ele>52.64</ele> +<time>2024-11-11T11:39:25Z</time> +</trkpt> +<trkpt lat="40.045444" lon="116.305679"><ele>52.52</ele> +<time>2024-11-11T11:39:26Z</time> +</trkpt> +<trkpt lat="40.045403" lon="116.305717"><ele>52.85</ele> +<time>2024-11-11T11:39:27Z</time> +</trkpt> +<trkpt lat="40.045361" lon="116.305748"><ele>52.85</ele> +<time>2024-11-11T11:39:28Z</time> +</trkpt> +<trkpt lat="40.045315" lon="116.305779"><ele>52.74</ele> +<time>2024-11-11T11:39:29Z</time> +</trkpt> +<trkpt lat="40.045273" lon="116.305809"><ele>52.72</ele> +<time>2024-11-11T11:39:30Z</time> +</trkpt> +<trkpt lat="40.045227" lon="116.305840"><ele>52.74</ele> +<time>2024-11-11T11:39:31Z</time> +</trkpt> +<trkpt lat="40.045181" lon="116.305878"><ele>53.02</ele> +<time>2024-11-11T11:39:32Z</time> +</trkpt> +<trkpt lat="40.045139" lon="116.305908"><ele>53.02</ele> +<time>2024-11-11T11:39:33Z</time> +</trkpt> +<trkpt lat="40.045097" lon="116.305946"><ele>53.02</ele> +<time>2024-11-11T11:39:34Z</time> +</trkpt> +<trkpt lat="40.045052" lon="116.305977"><ele>53.01</ele> +<time>2024-11-11T11:39:35Z</time> +</trkpt> +<trkpt lat="40.045006" lon="116.306015"><ele>53.01</ele> +<time>2024-11-11T11:39:36Z</time> +</trkpt> +<trkpt lat="40.044964" lon="116.306046"><ele>53.27</ele> +<time>2024-11-11T11:39:37Z</time> +</trkpt> +<trkpt lat="40.044918" lon="116.306084"><ele>53.33</ele> +<time>2024-11-11T11:39:38Z</time> +</trkpt> +<trkpt lat="40.044876" lon="116.306114"><ele>53.27</ele> +<time>2024-11-11T11:39:39Z</time> +</trkpt> +<trkpt lat="40.044830" lon="116.306152"><ele>53.27</ele> +<time>2024-11-11T11:39:40Z</time> +</trkpt> +<trkpt lat="40.044788" lon="116.306183"><ele>53.25</ele> +<time>2024-11-11T11:39:41Z</time> +</trkpt> +<trkpt lat="40.044746" lon="116.306221"><ele>53.27</ele> +<time>2024-11-11T11:39:42Z</time> +</trkpt> +<trkpt lat="40.044704" lon="116.306259"><ele>53.44</ele> +<time>2024-11-11T11:39:43Z</time> +</trkpt> +<trkpt lat="40.044662" lon="116.306297"><ele>53.44</ele> +<time>2024-11-11T11:39:44Z</time> +</trkpt> +<trkpt lat="40.044624" lon="116.306335"><ele>53.46</ele> +<time>2024-11-11T11:39:45Z</time> +</trkpt> +<trkpt lat="40.044586" lon="116.306374"><ele>53.59</ele> +<time>2024-11-11T11:39:46Z</time> +</trkpt> +<trkpt lat="40.044540" lon="116.306404"><ele>53.69</ele> +<time>2024-11-11T11:39:47Z</time> +</trkpt> +<trkpt lat="40.044498" lon="116.306442"><ele>53.71</ele> +<time>2024-11-11T11:39:48Z</time> +</trkpt> +<trkpt lat="40.044460" lon="116.306473"><ele>53.67</ele> +<time>2024-11-11T11:39:49Z</time> +</trkpt> +<trkpt lat="40.044418" lon="116.306503"><ele>53.67</ele> +<time>2024-11-11T11:39:50Z</time> +</trkpt> +<trkpt lat="40.044376" lon="116.306541"><ele>53.40</ele> +<time>2024-11-11T11:39:51Z</time> +</trkpt> +<trkpt lat="40.044334" lon="116.306572"><ele>53.42</ele> +<time>2024-11-11T11:39:52Z</time> +</trkpt> +<trkpt lat="40.044292" lon="116.306602"><ele>53.38</ele> +<time>2024-11-11T11:39:53Z</time> +</trkpt> +<trkpt lat="40.044247" lon="116.306633"><ele>53.29</ele> +<time>2024-11-11T11:39:54Z</time> +</trkpt> +<trkpt lat="40.044205" lon="116.306671"><ele>53.23</ele> +<time>2024-11-11T11:39:55Z</time> +</trkpt> +<trkpt lat="40.044159" lon="116.306702"><ele>53.07</ele> +<time>2024-11-11T11:39:56Z</time> +</trkpt> +<trkpt lat="40.044117" lon="116.306740"><ele>53.07</ele> +<time>2024-11-11T11:39:57Z</time> +</trkpt> +<trkpt lat="40.044075" lon="116.306778"><ele>53.02</ele> +<time>2024-11-11T11:39:58Z</time> +</trkpt> +<trkpt lat="40.044025" lon="116.306808"><ele>52.96</ele> +<time>2024-11-11T11:39:59Z</time> +</trkpt> +<trkpt lat="40.043980" lon="116.306847"><ele>52.96</ele> +<time>2024-11-11T11:40:00Z</time> +</trkpt> +<trkpt lat="40.043930" lon="116.306885"><ele>52.95</ele> +<time>2024-11-11T11:40:01Z</time> +</trkpt> +<trkpt lat="40.043877" lon="116.306915"><ele>52.95</ele> +<time>2024-11-11T11:40:02Z</time> +</trkpt> +<trkpt lat="40.043831" lon="116.306953"><ele>52.60</ele> +<time>2024-11-11T11:40:03Z</time> +</trkpt> +<trkpt lat="40.043781" lon="116.306992"><ele>52.60</ele> +<time>2024-11-11T11:40:04Z</time> +</trkpt> +<trkpt lat="40.043732" lon="116.307030"><ele>52.58</ele> +<time>2024-11-11T11:40:05Z</time> +</trkpt> +<trkpt lat="40.043682" lon="116.307060"><ele>52.58</ele> +<time>2024-11-11T11:40:06Z</time> +</trkpt> +<trkpt lat="40.043633" lon="116.307098"><ele>52.41</ele> +<time>2024-11-11T11:40:07Z</time> +</trkpt> +<trkpt lat="40.043587" lon="116.307137"><ele>52.52</ele> +<time>2024-11-11T11:40:08Z</time> +</trkpt> +<trkpt lat="40.043537" lon="116.307175"><ele>52.52</ele> +<time>2024-11-11T11:40:09Z</time> +</trkpt> +<trkpt lat="40.043491" lon="116.307213"><ele>52.53</ele> +<time>2024-11-11T11:40:10Z</time> +</trkpt> +<trkpt lat="40.043442" lon="116.307251"><ele>52.59</ele> +<time>2024-11-11T11:40:11Z</time> +</trkpt> +<trkpt lat="40.043392" lon="116.307281"><ele>52.59</ele> +<time>2024-11-11T11:40:12Z</time> +</trkpt> +<trkpt lat="40.043343" lon="116.307320"><ele>52.67</ele> +<time>2024-11-11T11:40:13Z</time> +</trkpt> +<trkpt lat="40.043297" lon="116.307350"><ele>52.58</ele> +<time>2024-11-11T11:40:14Z</time> +</trkpt> +<trkpt lat="40.043255" lon="116.307388"><ele>52.62</ele> +<time>2024-11-11T11:40:15Z</time> +</trkpt> +<trkpt lat="40.043213" lon="116.307426"><ele>52.65</ele> +<time>2024-11-11T11:40:16Z</time> +</trkpt> +<trkpt lat="40.043171" lon="116.307465"><ele>52.76</ele> +<time>2024-11-11T11:40:17Z</time> +</trkpt> +<trkpt lat="40.043121" lon="116.307510"><ele>52.81</ele> +<time>2024-11-11T11:40:18Z</time> +</trkpt> +<trkpt lat="40.043068" lon="116.307564"><ele>52.88</ele> +<time>2024-11-11T11:40:19Z</time> +</trkpt> +<trkpt lat="40.043034" lon="116.307617"><ele>53.08</ele> +<time>2024-11-11T11:40:20Z</time> +</trkpt> +<trkpt lat="40.043011" lon="116.307655"><ele>53.09</ele> +<time>2024-11-11T11:40:21Z</time> +</trkpt> +<trkpt lat="40.042995" lon="116.307678"><ele>53.08</ele> +<time>2024-11-11T11:40:22Z</time> +</trkpt> +<trkpt lat="40.042980" lon="116.307739"><ele>53.08</ele> +<time>2024-11-11T11:40:23Z</time> +</trkpt> +<trkpt lat="40.042961" lon="116.307808"><ele>53.31</ele> +<time>2024-11-11T11:40:24Z</time> +</trkpt> +<trkpt lat="40.042950" lon="116.307884"><ele>53.39</ele> +<time>2024-11-11T11:40:25Z</time> +</trkpt> +<trkpt lat="40.042953" lon="116.307945"><ele>53.58</ele> +<time>2024-11-11T11:40:26Z</time> +</trkpt> +<trkpt lat="40.042969" lon="116.307999"><ele>53.48</ele> +<time>2024-11-11T11:40:27Z</time> +</trkpt> +<trkpt lat="40.042980" lon="116.308029"><ele>53.44</ele> +<time>2024-11-11T11:40:28Z</time> +</trkpt> +<trkpt lat="40.043018" lon="116.308060"><ele>53.31</ele> +<time>2024-11-11T11:40:29Z</time> +</trkpt> +<trkpt lat="40.043053" lon="116.308121"><ele>53.28</ele> +<time>2024-11-11T11:40:30Z</time> +</trkpt> +<trkpt lat="40.043087" lon="116.308182"><ele>53.10</ele> +<time>2024-11-11T11:40:31Z</time> +</trkpt> +<trkpt lat="40.043118" lon="116.308243"><ele>52.64</ele> +<time>2024-11-11T11:40:32Z</time> +</trkpt> +<trkpt lat="40.043152" lon="116.308304"><ele>52.59</ele> +<time>2024-11-11T11:40:33Z</time> +</trkpt> +<trkpt lat="40.043182" lon="116.308365"><ele>52.36</ele> +<time>2024-11-11T11:40:34Z</time> +</trkpt> +<trkpt lat="40.043213" lon="116.308434"><ele>52.25</ele> +<time>2024-11-11T11:40:35Z</time> +</trkpt> +<trkpt lat="40.043243" lon="116.308487"><ele>52.25</ele> +<time>2024-11-11T11:40:36Z</time> +</trkpt> +<trkpt lat="40.043278" lon="116.308548"><ele>52.29</ele> +<time>2024-11-11T11:40:37Z</time> +</trkpt> +<trkpt lat="40.043316" lon="116.308601"><ele>52.28</ele> +<time>2024-11-11T11:40:38Z</time> +</trkpt> +<trkpt lat="40.043350" lon="116.308662"><ele>52.05</ele> +<time>2024-11-11T11:40:39Z</time> +</trkpt> +<trkpt lat="40.043388" lon="116.308716"><ele>52.05</ele> +<time>2024-11-11T11:40:40Z</time> +</trkpt> +<trkpt lat="40.043427" lon="116.308769"><ele>51.93</ele> +<time>2024-11-11T11:40:41Z</time> +</trkpt> +<trkpt lat="40.043461" lon="116.308830"><ele>51.93</ele> +<time>2024-11-11T11:40:42Z</time> +</trkpt> +<trkpt lat="40.043495" lon="116.308884"><ele>51.99</ele> +<time>2024-11-11T11:40:43Z</time> +</trkpt> +<trkpt lat="40.043533" lon="116.308937"><ele>51.93</ele> +<time>2024-11-11T11:40:44Z</time> +</trkpt> +<trkpt lat="40.043568" lon="116.308990"><ele>51.92</ele> +<time>2024-11-11T11:40:45Z</time> +</trkpt> +<trkpt lat="40.043602" lon="116.309044"><ele>51.88</ele> +<time>2024-11-11T11:40:46Z</time> +</trkpt> +<trkpt lat="40.043636" lon="116.309105"><ele>51.92</ele> +<time>2024-11-11T11:40:47Z</time> +</trkpt> +<trkpt lat="40.043671" lon="116.309158"><ele>51.88</ele> +<time>2024-11-11T11:40:48Z</time> +</trkpt> +<trkpt lat="40.043705" lon="116.309219"><ele>51.69</ele> +<time>2024-11-11T11:40:49Z</time> +</trkpt> +<trkpt lat="40.043736" lon="116.309273"><ele>51.12</ele> +<time>2024-11-11T11:40:50Z</time> +</trkpt> +<trkpt lat="40.043774" lon="116.309326"><ele>51.12</ele> +<time>2024-11-11T11:40:51Z</time> +</trkpt> +<trkpt lat="40.043808" lon="116.309387"><ele>51.39</ele> +<time>2024-11-11T11:40:52Z</time> +</trkpt> +<trkpt lat="40.043842" lon="116.309441"><ele>51.41</ele> +<time>2024-11-11T11:40:53Z</time> +</trkpt> +<trkpt lat="40.043877" lon="116.309494"><ele>51.66</ele> +<time>2024-11-11T11:40:54Z</time> +</trkpt> +<trkpt lat="40.043911" lon="116.309547"><ele>51.67</ele> +<time>2024-11-11T11:40:55Z</time> +</trkpt> +<trkpt lat="40.043949" lon="116.309608"><ele>51.95</ele> +<time>2024-11-11T11:40:56Z</time> +</trkpt> +<trkpt lat="40.043983" lon="116.309662"><ele>52.10</ele> +<time>2024-11-11T11:40:57Z</time> +</trkpt> +<trkpt lat="40.044018" lon="116.309723"><ele>52.10</ele> +<time>2024-11-11T11:40:58Z</time> +</trkpt> +<trkpt lat="40.044052" lon="116.309776"><ele>51.92</ele> +<time>2024-11-11T11:40:59Z</time> +</trkpt> +<trkpt lat="40.044086" lon="116.309837"><ele>51.77</ele> +<time>2024-11-11T11:41:00Z</time> +</trkpt> +<trkpt lat="40.044121" lon="116.309891"><ele>51.89</ele> +<time>2024-11-11T11:41:01Z</time> +</trkpt> +<trkpt lat="40.044159" lon="116.309952"><ele>51.69</ele> +<time>2024-11-11T11:41:02Z</time> +</trkpt> +<trkpt lat="40.044193" lon="116.310005"><ele>51.69</ele> +<time>2024-11-11T11:41:03Z</time> +</trkpt> +<trkpt lat="40.044228" lon="116.310059"><ele>51.96</ele> +<time>2024-11-11T11:41:04Z</time> +</trkpt> +<trkpt lat="40.044258" lon="116.310120"><ele>51.80</ele> +<time>2024-11-11T11:41:05Z</time> +</trkpt> +<trkpt lat="40.044292" lon="116.310181"><ele>51.97</ele> +<time>2024-11-11T11:41:06Z</time> +</trkpt> +<trkpt lat="40.044327" lon="116.310234"><ele>52.00</ele> +<time>2024-11-11T11:41:07Z</time> +</trkpt> +<trkpt lat="40.044357" lon="116.310287"><ele>52.00</ele> +<time>2024-11-11T11:41:08Z</time> +</trkpt> +<trkpt lat="40.044392" lon="116.310341"><ele>52.24</ele> +<time>2024-11-11T11:41:09Z</time> +</trkpt> +<trkpt lat="40.044422" lon="116.310379"><ele>52.24</ele> +<time>2024-11-11T11:41:10Z</time> +</trkpt> +<trkpt lat="40.044456" lon="116.310425"><ele>52.05</ele> +<time>2024-11-11T11:41:11Z</time> +</trkpt> +<trkpt lat="40.044495" lon="116.310471"><ele>52.05</ele> +<time>2024-11-11T11:41:12Z</time> +</trkpt> +<trkpt lat="40.044533" lon="116.310509"><ele>51.99</ele> +<time>2024-11-11T11:41:13Z</time> +</trkpt> +<trkpt lat="40.044571" lon="116.310539"><ele>51.94</ele> +<time>2024-11-11T11:41:14Z</time> +</trkpt> +<trkpt lat="40.044609" lon="116.310570"><ele>51.87</ele> +<time>2024-11-11T11:41:15Z</time> +</trkpt> +<trkpt lat="40.044651" lon="116.310600"><ele>51.89</ele> +<time>2024-11-11T11:41:16Z</time> +</trkpt> +<trkpt lat="40.044697" lon="116.310623"><ele>52.20</ele> +<time>2024-11-11T11:41:17Z</time> +</trkpt> +<trkpt lat="40.044743" lon="116.310638"><ele>52.24</ele> +<time>2024-11-11T11:41:18Z</time> +</trkpt> +<trkpt lat="40.044788" lon="116.310646"><ele>52.20</ele> +<time>2024-11-11T11:41:19Z</time> +</trkpt> +<trkpt lat="40.044838" lon="116.310646"><ele>52.15</ele> +<time>2024-11-11T11:41:20Z</time> +</trkpt> +<trkpt lat="40.044884" lon="116.310631"><ele>52.15</ele> +<time>2024-11-11T11:41:21Z</time> +</trkpt> +<trkpt lat="40.044933" lon="116.310616"><ele>52.23</ele> +<time>2024-11-11T11:41:22Z</time> +</trkpt> +<trkpt lat="40.044979" lon="116.310593"><ele>52.43</ele> +<time>2024-11-11T11:41:23Z</time> +</trkpt> +<trkpt lat="40.045029" lon="116.310555"><ele>52.44</ele> +<time>2024-11-11T11:41:24Z</time> +</trkpt> +<trkpt lat="40.045078" lon="116.310524"><ele>52.74</ele> +<time>2024-11-11T11:41:25Z</time> +</trkpt> +<trkpt lat="40.045128" lon="116.310486"><ele>52.74</ele> +<time>2024-11-11T11:41:26Z</time> +</trkpt> +<trkpt lat="40.045174" lon="116.310455"><ele>52.67</ele> +<time>2024-11-11T11:41:27Z</time> +</trkpt> +<trkpt lat="40.045223" lon="116.310425"><ele>52.67</ele> +<time>2024-11-11T11:41:28Z</time> +</trkpt> +<trkpt lat="40.045273" lon="116.310387"><ele>52.77</ele> +<time>2024-11-11T11:41:29Z</time> +</trkpt> +<trkpt lat="40.045322" lon="116.310356"><ele>52.78</ele> +<time>2024-11-11T11:41:30Z</time> +</trkpt> +<trkpt lat="40.045372" lon="116.310318"><ele>52.90</ele> +<time>2024-11-11T11:41:31Z</time> +</trkpt> +<trkpt lat="40.045422" lon="116.310287"><ele>52.95</ele> +<time>2024-11-11T11:41:32Z</time> +</trkpt> +<trkpt lat="40.045467" lon="116.310265"><ele>52.90</ele> +<time>2024-11-11T11:41:33Z</time> +</trkpt> +<trkpt lat="40.045521" lon="116.310234"><ele>52.72</ele> +<time>2024-11-11T11:41:34Z</time> +</trkpt> +<trkpt lat="40.045570" lon="116.310196"><ele>52.57</ele> +<time>2024-11-11T11:41:35Z</time> +</trkpt> +<trkpt lat="40.045616" lon="116.310165"><ele>52.57</ele> +<time>2024-11-11T11:41:36Z</time> +</trkpt> +<trkpt lat="40.045658" lon="116.310143"><ele>52.64</ele> +<time>2024-11-11T11:41:37Z</time> +</trkpt> +<trkpt lat="40.045700" lon="116.310127"><ele>52.99</ele> +<time>2024-11-11T11:41:38Z</time> +</trkpt> +<trkpt lat="40.045738" lon="116.310112"><ele>53.08</ele> +<time>2024-11-11T11:41:39Z</time> +</trkpt> +<trkpt lat="40.045780" lon="116.310081"><ele>53.08</ele> +<time>2024-11-11T11:41:40Z</time> +</trkpt> +<trkpt lat="40.045830" lon="116.310066"><ele>52.96</ele> +<time>2024-11-11T11:41:41Z</time> +</trkpt> +<trkpt lat="40.045879" lon="116.310051"><ele>52.81</ele> +<time>2024-11-11T11:41:42Z</time> +</trkpt> +<trkpt lat="40.045929" lon="116.310028"><ele>52.81</ele> +<time>2024-11-11T11:41:43Z</time> +</trkpt> +<trkpt lat="40.045979" lon="116.310013"><ele>52.81</ele> +<time>2024-11-11T11:41:44Z</time> +</trkpt> +<trkpt lat="40.046024" lon="116.310005"><ele>52.81</ele> +<time>2024-11-11T11:41:45Z</time> +</trkpt> +<trkpt lat="40.046066" lon="116.310005"><ele>52.36</ele> +<time>2024-11-11T11:41:46Z</time> +</trkpt> +<trkpt lat="40.046112" lon="116.309982"><ele>52.36</ele> +<time>2024-11-11T11:41:47Z</time> +</trkpt> +<trkpt lat="40.046158" lon="116.309952"><ele>52.27</ele> +<time>2024-11-11T11:41:48Z</time> +</trkpt> +<trkpt lat="40.046207" lon="116.309914"><ele>52.36</ele> +<time>2024-11-11T11:41:49Z</time> +</trkpt> +<trkpt lat="40.046249" lon="116.309883"><ele>51.98</ele> +<time>2024-11-11T11:41:50Z</time> +</trkpt> +<trkpt lat="40.046295" lon="116.309845"><ele>51.77</ele> +<time>2024-11-11T11:41:51Z</time> +</trkpt> +<trkpt lat="40.046345" lon="116.309830"><ele>51.87</ele> +<time>2024-11-11T11:41:52Z</time> +</trkpt> +<trkpt lat="40.046394" lon="116.309807"><ele>51.98</ele> +<time>2024-11-11T11:41:53Z</time> +</trkpt> +<trkpt lat="40.046440" lon="116.309792"><ele>52.12</ele> +<time>2024-11-11T11:41:54Z</time> +</trkpt> +<trkpt lat="40.046486" lon="116.309776"><ele>52.12</ele> +<time>2024-11-11T11:41:55Z</time> +</trkpt> +<trkpt lat="40.046524" lon="116.309753"><ele>52.37</ele> +<time>2024-11-11T11:41:56Z</time> +</trkpt> +<trkpt lat="40.046558" lon="116.309723"><ele>52.47</ele> +<time>2024-11-11T11:41:57Z</time> +</trkpt> +<trkpt lat="40.046585" lon="116.309708"><ele>52.75</ele> +<time>2024-11-11T11:41:58Z</time> +</trkpt> +<trkpt lat="40.046608" lon="116.309700"><ele>52.75</ele> +<time>2024-11-11T11:41:59Z</time> +</trkpt> +<trkpt lat="40.046635" lon="116.309708"><ele>52.75</ele> +<time>2024-11-11T11:42:00Z</time> +</trkpt> +<trkpt lat="40.046669" lon="116.309715"><ele>52.75</ele> +<time>2024-11-11T11:42:01Z</time> +</trkpt> +<trkpt lat="40.046700" lon="116.309731"><ele>52.58</ele> +<time>2024-11-11T11:42:02Z</time> +</trkpt> +<trkpt lat="40.046719" lon="116.309746"><ele>52.57</ele> +<time>2024-11-11T11:42:03Z</time> +</trkpt> +<trkpt lat="40.046719" lon="116.309753"><ele>52.56</ele> +<time>2024-11-11T11:42:04Z</time> +</trkpt> +<trkpt lat="40.046726" lon="116.309776"><ele>52.42</ele> +<time>2024-11-11T11:42:05Z</time> +</trkpt> +<trkpt lat="40.046738" lon="116.309807"><ele>52.28</ele> +<time>2024-11-11T11:42:06Z</time> +</trkpt> +<trkpt lat="40.046757" lon="116.309845"><ele>52.07</ele> +<time>2024-11-11T11:42:07Z</time> +</trkpt> +<trkpt lat="40.046776" lon="116.309883"><ele>52.07</ele> +<time>2024-11-11T11:42:08Z</time> +</trkpt> +<trkpt lat="40.046795" lon="116.309929"><ele>52.07</ele> +<time>2024-11-11T11:42:09Z</time> +</trkpt> +<trkpt lat="40.046814" lon="116.309975"><ele>52.06</ele> +<time>2024-11-11T11:42:10Z</time> +</trkpt> +<trkpt lat="40.046837" lon="116.310028"><ele>52.17</ele> +<time>2024-11-11T11:42:11Z</time> +</trkpt> +<trkpt lat="40.046864" lon="116.310081"><ele>52.17</ele> +<time>2024-11-11T11:42:12Z</time> +</trkpt> +<trkpt lat="40.046886" lon="116.310135"><ele>52.17</ele> +<time>2024-11-11T11:42:13Z</time> +</trkpt> +<trkpt lat="40.046913" lon="116.310196"><ele>52.20</ele> +<time>2024-11-11T11:42:14Z</time> +</trkpt> +<trkpt lat="40.046940" lon="116.310257"><ele>52.20</ele> +<time>2024-11-11T11:42:15Z</time> +</trkpt> +<trkpt lat="40.046963" lon="116.310318"><ele>52.20</ele> +<time>2024-11-11T11:42:16Z</time> +</trkpt> +<trkpt lat="40.046989" lon="116.310371"><ele>51.85</ele> +<time>2024-11-11T11:42:17Z</time> +</trkpt> +<trkpt lat="40.047016" lon="116.310432"><ele>51.85</ele> +<time>2024-11-11T11:42:18Z</time> +</trkpt> +<trkpt lat="40.047047" lon="116.310486"><ele>52.33</ele> +<time>2024-11-11T11:42:19Z</time> +</trkpt> +<trkpt lat="40.047073" lon="116.310547"><ele>52.39</ele> +<time>2024-11-11T11:42:20Z</time> +</trkpt> +<trkpt lat="40.047104" lon="116.310616"><ele>52.52</ele> +<time>2024-11-11T11:42:21Z</time> +</trkpt> +<trkpt lat="40.047134" lon="116.310677"><ele>52.50</ele> +<time>2024-11-11T11:42:22Z</time> +</trkpt> +<trkpt lat="40.047161" lon="116.310738"><ele>52.50</ele> +<time>2024-11-11T11:42:23Z</time> +</trkpt> +<trkpt lat="40.047192" lon="116.310806"><ele>52.50</ele> +<time>2024-11-11T11:42:24Z</time> +</trkpt> +<trkpt lat="40.047215" lon="116.310867"><ele>52.06</ele> +<time>2024-11-11T11:42:25Z</time> +</trkpt> +<trkpt lat="40.047234" lon="116.310928"><ele>52.06</ele> +<time>2024-11-11T11:42:26Z</time> +</trkpt> +<trkpt lat="40.047260" lon="116.310997"><ele>51.96</ele> +<time>2024-11-11T11:42:27Z</time> +</trkpt> +<trkpt lat="40.047287" lon="116.311050"><ele>51.92</ele> +<time>2024-11-11T11:42:28Z</time> +</trkpt> +<trkpt lat="40.047310" lon="116.311111"><ele>51.76</ele> +<time>2024-11-11T11:42:29Z</time> +</trkpt> +<trkpt lat="40.047337" lon="116.311172"><ele>51.74</ele> +<time>2024-11-11T11:42:30Z</time> +</trkpt> +<trkpt lat="40.047359" lon="116.311226"><ele>51.62</ele> +<time>2024-11-11T11:42:31Z</time> +</trkpt> +<trkpt lat="40.047386" lon="116.311287"><ele>51.62</ele> +<time>2024-11-11T11:42:32Z</time> +</trkpt> +<trkpt lat="40.047413" lon="116.311348"><ele>51.62</ele> +<time>2024-11-11T11:42:33Z</time> +</trkpt> +<trkpt lat="40.047440" lon="116.311409"><ele>51.86</ele> +<time>2024-11-11T11:42:34Z</time> +</trkpt> +<trkpt lat="40.047470" lon="116.311455"><ele>51.86</ele> +<time>2024-11-11T11:42:35Z</time> +</trkpt> +<trkpt lat="40.047501" lon="116.311516"><ele>51.81</ele> +<time>2024-11-11T11:42:36Z</time> +</trkpt> +<trkpt lat="40.047527" lon="116.311569"><ele>51.94</ele> +<time>2024-11-11T11:42:37Z</time> +</trkpt> +<trkpt lat="40.047543" lon="116.311615"><ele>51.94</ele> +<time>2024-11-11T11:42:38Z</time> +</trkpt> +<trkpt lat="40.047565" lon="116.311676"><ele>52.06</ele> +<time>2024-11-11T11:42:39Z</time> +</trkpt> +<trkpt lat="40.047596" lon="116.311729"><ele>52.06</ele> +<time>2024-11-11T11:42:40Z</time> +</trkpt> +<trkpt lat="40.047615" lon="116.311790"><ele>51.89</ele> +<time>2024-11-11T11:42:41Z</time> +</trkpt> +<trkpt lat="40.047646" lon="116.311844"><ele>51.62</ele> +<time>2024-11-11T11:42:42Z</time> +</trkpt> +<trkpt lat="40.047672" lon="116.311897"><ele>51.62</ele> +<time>2024-11-11T11:42:43Z</time> +</trkpt> +<trkpt lat="40.047699" lon="116.311958"><ele>51.62</ele> +<time>2024-11-11T11:42:44Z</time> +</trkpt> +<trkpt lat="40.047726" lon="116.312012"><ele>51.92</ele> +<time>2024-11-11T11:42:45Z</time> +</trkpt> +<trkpt lat="40.047752" lon="116.312065"><ele>52.03</ele> +<time>2024-11-11T11:42:46Z</time> +</trkpt> +<trkpt lat="40.047775" lon="116.312119"><ele>52.15</ele> +<time>2024-11-11T11:42:47Z</time> +</trkpt> +<trkpt lat="40.047798" lon="116.312164"><ele>52.20</ele> +<time>2024-11-11T11:42:48Z</time> +</trkpt> +<trkpt lat="40.047817" lon="116.312210"><ele>52.21</ele> +<time>2024-11-11T11:42:49Z</time> +</trkpt> +<trkpt lat="40.047844" lon="116.312248"><ele>52.29</ele> +<time>2024-11-11T11:42:50Z</time> +</trkpt> +<trkpt lat="40.047871" lon="116.312294"><ele>52.34</ele> +<time>2024-11-11T11:42:51Z</time> +</trkpt> +<trkpt lat="40.047886" lon="116.312340"><ele>52.43</ele> +<time>2024-11-11T11:42:52Z</time> +</trkpt> +<trkpt lat="40.047890" lon="116.312386"><ele>52.43</ele> +<time>2024-11-11T11:42:53Z</time> +</trkpt> +<trkpt lat="40.047871" lon="116.312431"><ele>52.43</ele> +<time>2024-11-11T11:42:54Z</time> +</trkpt> +<trkpt lat="40.047848" lon="116.312469"><ele>52.44</ele> +<time>2024-11-11T11:42:55Z</time> +</trkpt> +<trkpt lat="40.047813" lon="116.312500"><ele>52.36</ele> +<time>2024-11-11T11:42:56Z</time> +</trkpt> +<trkpt lat="40.047771" lon="116.312538"><ele>52.30</ele> +<time>2024-11-11T11:42:57Z</time> +</trkpt> +<trkpt lat="40.047726" lon="116.312553"><ele>52.14</ele> +<time>2024-11-11T11:42:58Z</time> +</trkpt> +<trkpt lat="40.047680" lon="116.312561"><ele>52.10</ele> +<time>2024-11-11T11:42:59Z</time> +</trkpt> +<trkpt lat="40.047634" lon="116.312584"><ele>51.79</ele> +<time>2024-11-11T11:43:00Z</time> +</trkpt> +<trkpt lat="40.047588" lon="116.312607"><ele>51.79</ele> +<time>2024-11-11T11:43:01Z</time> +</trkpt> +<trkpt lat="40.047543" lon="116.312614"><ele>52.07</ele> +<time>2024-11-11T11:43:02Z</time> +</trkpt> +<trkpt lat="40.047493" lon="116.312637"><ele>52.07</ele> +<time>2024-11-11T11:43:03Z</time> +</trkpt> +<trkpt lat="40.047447" lon="116.312653"><ele>51.93</ele> +<time>2024-11-11T11:43:04Z</time> +</trkpt> +<trkpt lat="40.047405" lon="116.312668"><ele>51.86</ele> +<time>2024-11-11T11:43:05Z</time> +</trkpt> +<trkpt lat="40.047359" lon="116.312691"><ele>51.71</ele> +<time>2024-11-11T11:43:06Z</time> +</trkpt> +<trkpt lat="40.047310" lon="116.312714"><ele>51.86</ele> +<time>2024-11-11T11:43:07Z</time> +</trkpt> +<trkpt lat="40.047260" lon="116.312729"><ele>51.96</ele> +<time>2024-11-11T11:43:08Z</time> +</trkpt> +<trkpt lat="40.047211" lon="116.312752"><ele>51.96</ele> +<time>2024-11-11T11:43:09Z</time> +</trkpt> +<trkpt lat="40.047157" lon="116.312775"><ele>51.86</ele> +<time>2024-11-11T11:43:10Z</time> +</trkpt> +<trkpt lat="40.047096" lon="116.312798"><ele>51.86</ele> +<time>2024-11-11T11:43:11Z</time> +</trkpt> +<trkpt lat="40.047050" lon="116.312813"><ele>51.95</ele> +<time>2024-11-11T11:43:12Z</time> +</trkpt> +<trkpt lat="40.047001" lon="116.312843"><ele>52.07</ele> +<time>2024-11-11T11:43:13Z</time> +</trkpt> +<trkpt lat="40.046959" lon="116.312866"><ele>52.20</ele> +<time>2024-11-11T11:43:14Z</time> +</trkpt> +<trkpt lat="40.046921" lon="116.312889"><ele>52.22</ele> +<time>2024-11-11T11:43:15Z</time> +</trkpt> +<trkpt lat="40.046879" lon="116.312912"><ele>52.43</ele> +<time>2024-11-11T11:43:16Z</time> +</trkpt> +<trkpt lat="40.046837" lon="116.312920"><ele>52.43</ele> +<time>2024-11-11T11:43:17Z</time> +</trkpt> +<trkpt lat="40.046803" lon="116.312935"><ele>52.37</ele> +<time>2024-11-11T11:43:18Z</time> +</trkpt> +<trkpt lat="40.046761" lon="116.312950"><ele>52.22</ele> +<time>2024-11-11T11:43:19Z</time> +</trkpt> +<trkpt lat="40.046722" lon="116.312973"><ele>52.21</ele> +<time>2024-11-11T11:43:20Z</time> +</trkpt> +<trkpt lat="40.046688" lon="116.312988"><ele>52.22</ele> +<time>2024-11-11T11:43:21Z</time> +</trkpt> +<trkpt lat="40.046650" lon="116.313004"><ele>52.45</ele> +<time>2024-11-11T11:43:22Z</time> +</trkpt> +<trkpt lat="40.046619" lon="116.313011"><ele>52.51</ele> +<time>2024-11-11T11:43:23Z</time> +</trkpt> +<trkpt lat="40.046589" lon="116.313026"><ele>52.54</ele> +<time>2024-11-11T11:43:24Z</time> +</trkpt> +<trkpt lat="40.046566" lon="116.313026"><ele>52.65</ele> +<time>2024-11-11T11:43:25Z</time> +</trkpt> +<trkpt lat="40.046539" lon="116.313026"><ele>52.65</ele> +<time>2024-11-11T11:43:26Z</time> +</trkpt> +<trkpt lat="40.046513" lon="116.313019"><ele>52.72</ele> +<time>2024-11-11T11:43:27Z</time> +</trkpt> +<trkpt lat="40.046486" lon="116.313011"><ele>52.65</ele> +<time>2024-11-11T11:43:28Z</time> +</trkpt> +<trkpt lat="40.046459" lon="116.312988"><ele>52.63</ele> +<time>2024-11-11T11:43:29Z</time> +</trkpt> +<trkpt lat="40.046436" lon="116.312958"><ele>52.63</ele> +<time>2024-11-11T11:43:30Z</time> +</trkpt> +<trkpt lat="40.046406" lon="116.312920"><ele>52.54</ele> +<time>2024-11-11T11:43:31Z</time> +</trkpt> +<trkpt lat="40.046375" lon="116.312881"><ele>52.51</ele> +<time>2024-11-11T11:43:32Z</time> +</trkpt> +<trkpt lat="40.046349" lon="116.312836"><ele>52.33</ele> +<time>2024-11-11T11:43:33Z</time> +</trkpt> +<trkpt lat="40.046314" lon="116.312790"><ele>52.33</ele> +<time>2024-11-11T11:43:34Z</time> +</trkpt> +<trkpt lat="40.046284" lon="116.312744"><ele>52.24</ele> +<time>2024-11-11T11:43:35Z</time> +</trkpt> +<trkpt lat="40.046253" lon="116.312698"><ele>52.35</ele> +<time>2024-11-11T11:43:36Z</time> +</trkpt> +<trkpt lat="40.046219" lon="116.312645"><ele>52.35</ele> +<time>2024-11-11T11:43:37Z</time> +</trkpt> +<trkpt lat="40.046185" lon="116.312599"><ele>52.35</ele> +<time>2024-11-11T11:43:38Z</time> +</trkpt> +<trkpt lat="40.046150" lon="116.312561"><ele>52.38</ele> +<time>2024-11-11T11:43:39Z</time> +</trkpt> +<trkpt lat="40.046112" lon="116.312515"><ele>52.33</ele> +<time>2024-11-11T11:43:40Z</time> +</trkpt> +<trkpt lat="40.046078" lon="116.312469"><ele>52.36</ele> +<time>2024-11-11T11:43:41Z</time> +</trkpt> +<trkpt lat="40.046043" lon="116.312424"><ele>52.36</ele> +<time>2024-11-11T11:43:42Z</time> +</trkpt> +<trkpt lat="40.046001" lon="116.312378"><ele>52.36</ele> +<time>2024-11-11T11:43:43Z</time> +</trkpt> +<trkpt lat="40.045963" lon="116.312332"><ele>52.35</ele> +<time>2024-11-11T11:43:44Z</time> +</trkpt> +<trkpt lat="40.045929" lon="116.312286"><ele>52.22</ele> +<time>2024-11-11T11:43:45Z</time> +</trkpt> +<trkpt lat="40.045898" lon="116.312233"><ele>52.19</ele> +<time>2024-11-11T11:43:46Z</time> +</trkpt> +<trkpt lat="40.045860" lon="116.312180"><ele>52.19</ele> +<time>2024-11-11T11:43:47Z</time> +</trkpt> +<trkpt lat="40.045826" lon="116.312134"><ele>52.17</ele> +<time>2024-11-11T11:43:48Z</time> +</trkpt> +<trkpt lat="40.045788" lon="116.312080"><ele>52.16</ele> +<time>2024-11-11T11:43:49Z</time> +</trkpt> +<trkpt lat="40.045750" lon="116.312035"><ele>52.16</ele> +<time>2024-11-11T11:43:50Z</time> +</trkpt> +<trkpt lat="40.045712" lon="116.311981"><ele>52.03</ele> +<time>2024-11-11T11:43:51Z</time> +</trkpt> +<trkpt lat="40.045673" lon="116.311935"><ele>52.00</ele> +<time>2024-11-11T11:43:52Z</time> +</trkpt> +<trkpt lat="40.045639" lon="116.311882"><ele>51.98</ele> +<time>2024-11-11T11:43:53Z</time> +</trkpt> +<trkpt lat="40.045612" lon="116.311836"><ele>51.94</ele> +<time>2024-11-11T11:43:54Z</time> +</trkpt> +<trkpt lat="40.045582" lon="116.311783"><ele>51.95</ele> +<time>2024-11-11T11:43:55Z</time> +</trkpt> +<trkpt lat="40.045547" lon="116.311729"><ele>51.92</ele> +<time>2024-11-11T11:43:56Z</time> +</trkpt> +<trkpt lat="40.045509" lon="116.311676"><ele>51.94</ele> +<time>2024-11-11T11:43:57Z</time> +</trkpt> +<trkpt lat="40.045475" lon="116.311623"><ele>51.92</ele> +<time>2024-11-11T11:43:58Z</time> +</trkpt> +<trkpt lat="40.045441" lon="116.311569"><ele>52.04</ele> +<time>2024-11-11T11:43:59Z</time> +</trkpt> +<trkpt lat="40.045410" lon="116.311516"><ele>51.98</ele> +<time>2024-11-11T11:44:00Z</time> +</trkpt> +<trkpt lat="40.045376" lon="116.311462"><ele>51.98</ele> +<time>2024-11-11T11:44:01Z</time> +</trkpt> +<trkpt lat="40.045345" lon="116.311409"><ele>51.83</ele> +<time>2024-11-11T11:44:02Z</time> +</trkpt> +<trkpt lat="40.045315" lon="116.311356"><ele>51.96</ele> +<time>2024-11-11T11:44:03Z</time> +</trkpt> +<trkpt lat="40.045284" lon="116.311310"><ele>51.97</ele> +<time>2024-11-11T11:44:04Z</time> +</trkpt> +<trkpt lat="40.045254" lon="116.311256"><ele>52.06</ele> +<time>2024-11-11T11:44:05Z</time> +</trkpt> +<trkpt lat="40.045223" lon="116.311211"><ele>52.23</ele> +<time>2024-11-11T11:44:06Z</time> +</trkpt> +<trkpt lat="40.045193" lon="116.311165"><ele>52.23</ele> +<time>2024-11-11T11:44:07Z</time> +</trkpt> +<trkpt lat="40.045158" lon="116.311119"><ele>52.23</ele> +<time>2024-11-11T11:44:08Z</time> +</trkpt> +<trkpt lat="40.045124" lon="116.311073"><ele>52.30</ele> +<time>2024-11-11T11:44:09Z</time> +</trkpt> +<trkpt lat="40.045094" lon="116.311028"><ele>52.30</ele> +<time>2024-11-11T11:44:10Z</time> +</trkpt> +<trkpt lat="40.045059" lon="116.310982"><ele>52.28</ele> +<time>2024-11-11T11:44:11Z</time> +</trkpt> +<trkpt lat="40.045036" lon="116.310936"><ele>52.18</ele> +<time>2024-11-11T11:44:12Z</time> +</trkpt> +<trkpt lat="40.045013" lon="116.310883"><ele>52.10</ele> +<time>2024-11-11T11:44:13Z</time> +</trkpt> +<trkpt lat="40.045010" lon="116.310829"><ele>51.94</ele> +<time>2024-11-11T11:44:14Z</time> +</trkpt> +<trkpt lat="40.045021" lon="116.310776"><ele>51.94</ele> +<time>2024-11-11T11:44:15Z</time> +</trkpt> +<trkpt lat="40.045040" lon="116.310745"><ele>51.73</ele> +<time>2024-11-11T11:44:16Z</time> +</trkpt> +<trkpt lat="40.045067" lon="116.310722"><ele>51.51</ele> +<time>2024-11-11T11:44:17Z</time> +</trkpt> +<trkpt lat="40.045105" lon="116.310707"><ele>51.44</ele> +<time>2024-11-11T11:44:18Z</time> +</trkpt> +<trkpt lat="40.045147" lon="116.310684"><ele>51.44</ele> +<time>2024-11-11T11:44:19Z</time> +</trkpt> +<trkpt lat="40.045189" lon="116.310654"><ele>51.38</ele> +<time>2024-11-11T11:44:20Z</time> +</trkpt> +<trkpt lat="40.045231" lon="116.310623"><ele>51.44</ele> +<time>2024-11-11T11:44:21Z</time> +</trkpt> +<trkpt lat="40.045277" lon="116.310585"><ele>51.38</ele> +<time>2024-11-11T11:44:22Z</time> +</trkpt> +<trkpt lat="40.045322" lon="116.310555"><ele>51.28</ele> +<time>2024-11-11T11:44:23Z</time> +</trkpt> +<trkpt lat="40.045364" lon="116.310532"><ele>51.28</ele> +<time>2024-11-11T11:44:24Z</time> +</trkpt> +<trkpt lat="40.045406" lon="116.310493"><ele>51.69</ele> +<time>2024-11-11T11:44:25Z</time> +</trkpt> +<trkpt lat="40.045452" lon="116.310463"><ele>51.81</ele> +<time>2024-11-11T11:44:26Z</time> +</trkpt> +<trkpt lat="40.045498" lon="116.310432"><ele>51.81</ele> +<time>2024-11-11T11:44:27Z</time> +</trkpt> +<trkpt lat="40.045544" lon="116.310402"><ele>51.77</ele> +<time>2024-11-11T11:44:28Z</time> +</trkpt> +<trkpt lat="40.045586" lon="116.310371"><ele>51.81</ele> +<time>2024-11-11T11:44:29Z</time> +</trkpt> +<trkpt lat="40.045628" lon="116.310333"><ele>51.85</ele> +<time>2024-11-11T11:44:30Z</time> +</trkpt> +<trkpt lat="40.045673" lon="116.310295"><ele>51.85</ele> +<time>2024-11-11T11:44:31Z</time> +</trkpt> +<trkpt lat="40.045715" lon="116.310265"><ele>51.57</ele> +<time>2024-11-11T11:44:32Z</time> +</trkpt> +<trkpt lat="40.045757" lon="116.310242"><ele>51.57</ele> +<time>2024-11-11T11:44:33Z</time> +</trkpt> +<trkpt lat="40.045788" lon="116.310211"><ele>51.72</ele> +<time>2024-11-11T11:44:34Z</time> +</trkpt> +<trkpt lat="40.045822" lon="116.310188"><ele>51.78</ele> +<time>2024-11-11T11:44:35Z</time> +</trkpt> +<trkpt lat="40.045856" lon="116.310158"><ele>52.14</ele> +<time>2024-11-11T11:44:36Z</time> +</trkpt> +<trkpt lat="40.045879" lon="116.310143"><ele>52.14</ele> +<time>2024-11-11T11:44:37Z</time> +</trkpt> +<trkpt lat="40.045902" lon="116.310127"><ele>51.99</ele> +<time>2024-11-11T11:44:38Z</time> +</trkpt> +<trkpt lat="40.045937" lon="116.310104"><ele>51.87</ele> +<time>2024-11-11T11:44:39Z</time> +</trkpt> +<trkpt lat="40.045971" lon="116.310081"><ele>51.74</ele> +<time>2024-11-11T11:44:40Z</time> +</trkpt> +<trkpt lat="40.046009" lon="116.310043"><ele>51.63</ele> +<time>2024-11-11T11:44:41Z</time> +</trkpt> +<trkpt lat="40.046047" lon="116.310020"><ele>51.53</ele> +<time>2024-11-11T11:44:42Z</time> +</trkpt> +<trkpt lat="40.046085" lon="116.309998"><ele>51.36</ele> +<time>2024-11-11T11:44:43Z</time> +</trkpt> +<trkpt lat="40.046127" lon="116.309982"><ele>51.36</ele> +<time>2024-11-11T11:44:44Z</time> +</trkpt> +<trkpt lat="40.046162" lon="116.309975"><ele>51.25</ele> +<time>2024-11-11T11:44:45Z</time> +</trkpt> +<trkpt lat="40.046200" lon="116.309975"><ele>51.23</ele> +<time>2024-11-11T11:44:46Z</time> +</trkpt> +<trkpt lat="40.046242" lon="116.309952"><ele>51.32</ele> +<time>2024-11-11T11:44:47Z</time> +</trkpt> +<trkpt lat="40.046284" lon="116.309914"><ele>51.32</ele> +<time>2024-11-11T11:44:48Z</time> +</trkpt> +<trkpt lat="40.046326" lon="116.309891"><ele>51.32</ele> +<time>2024-11-11T11:44:49Z</time> +</trkpt> +<trkpt lat="40.046368" lon="116.309875"><ele>51.32</ele> +<time>2024-11-11T11:44:50Z</time> +</trkpt> +<trkpt lat="40.046410" lon="116.309837"><ele>51.19</ele> +<time>2024-11-11T11:44:51Z</time> +</trkpt> +<trkpt lat="40.046429" lon="116.309845"><ele>51.22</ele> +<time>2024-11-11T11:44:52Z</time> +</trkpt> +<trkpt lat="40.046444" lon="116.309845"><ele>51.23</ele> +<time>2024-11-11T11:44:53Z</time> +</trkpt> +<trkpt lat="40.046486" lon="116.309830"><ele>51.41</ele> +<time>2024-11-11T11:44:54Z</time> +</trkpt> +<trkpt lat="40.046513" lon="116.309822"><ele>51.43</ele> +<time>2024-11-11T11:44:55Z</time> +</trkpt> +<trkpt lat="40.046543" lon="116.309814"><ele>51.83</ele> +<time>2024-11-11T11:44:56Z</time> +</trkpt> +<trkpt lat="40.046566" lon="116.309784"><ele>51.83</ele> +<time>2024-11-11T11:44:57Z</time> +</trkpt> +<trkpt lat="40.046597" lon="116.309769"><ele>51.89</ele> +<time>2024-11-11T11:44:58Z</time> +</trkpt> +<trkpt lat="40.046627" lon="116.309746"><ele>51.97</ele> +<time>2024-11-11T11:44:59Z</time> +</trkpt> +<trkpt lat="40.046658" lon="116.309731"><ele>52.08</ele> +<time>2024-11-11T11:45:00Z</time> +</trkpt> +<trkpt lat="40.046684" lon="116.309700"><ele>52.08</ele> +<time>2024-11-11T11:45:01Z</time> +</trkpt> +<trkpt lat="40.046711" lon="116.309677"><ele>52.03</ele> +<time>2024-11-11T11:45:02Z</time> +</trkpt> +<trkpt lat="40.046734" lon="116.309647"><ele>51.96</ele> +<time>2024-11-11T11:45:03Z</time> +</trkpt> +<trkpt lat="40.046757" lon="116.309616"><ele>51.85</ele> +<time>2024-11-11T11:45:04Z</time> +</trkpt> +<trkpt lat="40.046776" lon="116.309570"><ele>51.83</ele> +<time>2024-11-11T11:45:05Z</time> +</trkpt> +<trkpt lat="40.046791" lon="116.309509"><ele>51.63</ele> +<time>2024-11-11T11:45:06Z</time> +</trkpt> +<trkpt lat="40.046803" lon="116.309456"><ele>51.57</ele> +<time>2024-11-11T11:45:07Z</time> +</trkpt> +<trkpt lat="40.046810" lon="116.309395"><ele>51.57</ele> +<time>2024-11-11T11:45:08Z</time> +</trkpt> +<trkpt lat="40.046822" lon="116.309334"><ele>51.57</ele> +<time>2024-11-11T11:45:09Z</time> +</trkpt> +<trkpt lat="40.046833" lon="116.309280"><ele>51.57</ele> +<time>2024-11-11T11:45:10Z</time> +</trkpt> +<trkpt lat="40.046829" lon="116.309227"><ele>51.46</ele> +<time>2024-11-11T11:45:11Z</time> +</trkpt> +<trkpt lat="40.046814" lon="116.309166"><ele>51.43</ele> +<time>2024-11-11T11:45:12Z</time> +</trkpt> +<trkpt lat="40.046799" lon="116.309090"><ele>51.35</ele> +<time>2024-11-11T11:45:13Z</time> +</trkpt> +<trkpt lat="40.046787" lon="116.309021"><ele>51.03</ele> +<time>2024-11-11T11:45:14Z</time> +</trkpt> +<trkpt lat="40.046768" lon="116.308952"><ele>50.91</ele> +<time>2024-11-11T11:45:15Z</time> +</trkpt> +<trkpt lat="40.046753" lon="116.308884"><ele>50.91</ele> +<time>2024-11-11T11:45:16Z</time> +</trkpt> +<trkpt lat="40.046738" lon="116.308815"><ele>51.10</ele> +<time>2024-11-11T11:45:17Z</time> +</trkpt> +<trkpt lat="40.046719" lon="116.308746"><ele>51.22</ele> +<time>2024-11-11T11:45:18Z</time> +</trkpt> +<trkpt lat="40.046703" lon="116.308670"><ele>51.17</ele> +<time>2024-11-11T11:45:19Z</time> +</trkpt> +<trkpt lat="40.046692" lon="116.308601"><ele>51.01</ele> +<time>2024-11-11T11:45:20Z</time> +</trkpt> +<trkpt lat="40.046673" lon="116.308533"><ele>51.01</ele> +<time>2024-11-11T11:45:21Z</time> +</trkpt> +<trkpt lat="40.046661" lon="116.308464"><ele>51.17</ele> +<time>2024-11-11T11:45:22Z</time> +</trkpt> +<trkpt lat="40.046646" lon="116.308395"><ele>51.17</ele> +<time>2024-11-11T11:45:23Z</time> +</trkpt> +<trkpt lat="40.046631" lon="116.308327"><ele>51.17</ele> +<time>2024-11-11T11:45:24Z</time> +</trkpt> +<trkpt lat="40.046619" lon="116.308258"><ele>51.17</ele> +<time>2024-11-11T11:45:25Z</time> +</trkpt> +<trkpt lat="40.046604" lon="116.308189"><ele>51.18</ele> +<time>2024-11-11T11:45:26Z</time> +</trkpt> +<trkpt lat="40.046589" lon="116.308113"><ele>51.26</ele> +<time>2024-11-11T11:45:27Z</time> +</trkpt> +<trkpt lat="40.046574" lon="116.308044"><ele>51.30</ele> +<time>2024-11-11T11:45:28Z</time> +</trkpt> +<trkpt lat="40.046558" lon="116.307976"><ele>51.27</ele> +<time>2024-11-11T11:45:29Z</time> +</trkpt> +<trkpt lat="40.046543" lon="116.307907"><ele>51.09</ele> +<time>2024-11-11T11:45:30Z</time> +</trkpt> +<trkpt lat="40.046524" lon="116.307838"><ele>51.09</ele> +<time>2024-11-11T11:45:31Z</time> +</trkpt> +<trkpt lat="40.046513" lon="116.307762"><ele>51.10</ele> +<time>2024-11-11T11:45:32Z</time> +</trkpt> +<trkpt lat="40.046501" lon="116.307693"><ele>51.25</ele> +<time>2024-11-11T11:45:33Z</time> +</trkpt> +<trkpt lat="40.046486" lon="116.307625"><ele>51.32</ele> +<time>2024-11-11T11:45:34Z</time> +</trkpt> +<trkpt lat="40.046467" lon="116.307564"><ele>51.36</ele> +<time>2024-11-11T11:45:35Z</time> +</trkpt> +<trkpt lat="40.046448" lon="116.307487"><ele>51.58</ele> +<time>2024-11-11T11:45:36Z</time> +</trkpt> +<trkpt lat="40.046440" lon="116.307426"><ele>51.64</ele> +<time>2024-11-11T11:45:37Z</time> +</trkpt> +<trkpt lat="40.046421" lon="116.307350"><ele>51.64</ele> +<time>2024-11-11T11:45:38Z</time> +</trkpt> +<trkpt lat="40.046406" lon="116.307281"><ele>51.64</ele> +<time>2024-11-11T11:45:39Z</time> +</trkpt> +<trkpt lat="40.046391" lon="116.307213"><ele>51.27</ele> +<time>2024-11-11T11:45:40Z</time> +</trkpt> +<trkpt lat="40.046379" lon="116.307144"><ele>51.22</ele> +<time>2024-11-11T11:45:41Z</time> +</trkpt> +<trkpt lat="40.046368" lon="116.307076"><ele>51.22</ele> +<time>2024-11-11T11:45:42Z</time> +</trkpt> +<trkpt lat="40.046364" lon="116.307014"><ele>51.21</ele> +<time>2024-11-11T11:45:43Z</time> +</trkpt> +<trkpt lat="40.046352" lon="116.306946"><ele>51.02</ele> +<time>2024-11-11T11:45:44Z</time> +</trkpt> +<trkpt lat="40.046337" lon="116.306877"><ele>50.95</ele> +<time>2024-11-11T11:45:45Z</time> +</trkpt> +<trkpt lat="40.046318" lon="116.306808"><ele>50.79</ele> +<time>2024-11-11T11:45:46Z</time> +</trkpt> +<trkpt lat="40.046303" lon="116.306740"><ele>50.79</ele> +<time>2024-11-11T11:45:47Z</time> +</trkpt> +<trkpt lat="40.046284" lon="116.306671"><ele>50.96</ele> +<time>2024-11-11T11:45:48Z</time> +</trkpt> +<trkpt lat="40.046268" lon="116.306602"><ele>50.96</ele> +<time>2024-11-11T11:45:49Z</time> +</trkpt> +<trkpt lat="40.046253" lon="116.306526"><ele>51.07</ele> +<time>2024-11-11T11:45:50Z</time> +</trkpt> +<trkpt lat="40.046234" lon="116.306458"><ele>51.13</ele> +<time>2024-11-11T11:45:51Z</time> +</trkpt> +<trkpt lat="40.046211" lon="116.306389"><ele>51.13</ele> +<time>2024-11-11T11:45:52Z</time> +</trkpt> +<trkpt lat="40.046196" lon="116.306320"><ele>51.04</ele> +<time>2024-11-11T11:45:53Z</time> +</trkpt> +<trkpt lat="40.046177" lon="116.306244"><ele>50.98</ele> +<time>2024-11-11T11:45:54Z</time> +</trkpt> +<trkpt lat="40.046158" lon="116.306175"><ele>51.02</ele> +<time>2024-11-11T11:45:55Z</time> +</trkpt> +<trkpt lat="40.046143" lon="116.306099"><ele>50.93</ele> +<time>2024-11-11T11:45:56Z</time> +</trkpt> +<trkpt lat="40.046124" lon="116.306030"><ele>50.93</ele> +<time>2024-11-11T11:45:57Z</time> +</trkpt> +<trkpt lat="40.046104" lon="116.305962"><ele>50.85</ele> +<time>2024-11-11T11:45:58Z</time> +</trkpt> +<trkpt lat="40.046089" lon="116.305893"><ele>50.85</ele> +<time>2024-11-11T11:45:59Z</time> +</trkpt> +<trkpt lat="40.046070" lon="116.305832"><ele>50.66</ele> +<time>2024-11-11T11:46:00Z</time> +</trkpt> +<trkpt lat="40.046040" lon="116.305771"><ele>50.66</ele> +<time>2024-11-11T11:46:01Z</time> +</trkpt> +<trkpt lat="40.046001" lon="116.305725"><ele>50.73</ele> +<time>2024-11-11T11:46:02Z</time> +</trkpt> +<trkpt lat="40.045959" lon="116.305687"><ele>50.82</ele> +<time>2024-11-11T11:46:03Z</time> +</trkpt> +<trkpt lat="40.045910" lon="116.305664"><ele>50.92</ele> +<time>2024-11-11T11:46:04Z</time> +</trkpt> +<trkpt lat="40.045860" lon="116.305649"><ele>51.05</ele> +<time>2024-11-11T11:46:05Z</time> +</trkpt> +<trkpt lat="40.045811" lon="116.305656"><ele>51.05</ele> +<time>2024-11-11T11:46:06Z</time> +</trkpt> +<trkpt lat="40.045765" lon="116.305672"><ele>51.10</ele> +<time>2024-11-11T11:46:07Z</time> +</trkpt> +<trkpt lat="40.045723" lon="116.305695"><ele>51.41</ele> +<time>2024-11-11T11:46:08Z</time> +</trkpt> +<trkpt lat="40.045677" lon="116.305717"><ele>51.44</ele> +<time>2024-11-11T11:46:09Z</time> +</trkpt> +<trkpt lat="40.045628" lon="116.305748"><ele>51.55</ele> +<time>2024-11-11T11:46:10Z</time> +</trkpt> +<trkpt lat="40.045586" lon="116.305771"><ele>51.55</ele> +<time>2024-11-11T11:46:11Z</time> +</trkpt> +<trkpt lat="40.045540" lon="116.305801"><ele>51.57</ele> +<time>2024-11-11T11:46:12Z</time> +</trkpt> +<trkpt lat="40.045498" lon="116.305832"><ele>51.61</ele> +<time>2024-11-11T11:46:13Z</time> +</trkpt> +<trkpt lat="40.045452" lon="116.305862"><ele>51.82</ele> +<time>2024-11-11T11:46:14Z</time> +</trkpt> +<trkpt lat="40.045406" lon="116.305901"><ele>51.85</ele> +<time>2024-11-11T11:46:15Z</time> +</trkpt> +<trkpt lat="40.045361" lon="116.305931"><ele>51.85</ele> +<time>2024-11-11T11:46:16Z</time> +</trkpt> +<trkpt lat="40.045315" lon="116.305962"><ele>51.99</ele> +<time>2024-11-11T11:46:17Z</time> +</trkpt> +<trkpt lat="40.045269" lon="116.305992"><ele>52.08</ele> +<time>2024-11-11T11:46:18Z</time> +</trkpt> +<trkpt lat="40.045219" lon="116.306023"><ele>52.09</ele> +<time>2024-11-11T11:46:19Z</time> +</trkpt> +<trkpt lat="40.045174" lon="116.306053"><ele>52.08</ele> +<time>2024-11-11T11:46:20Z</time> +</trkpt> +<trkpt lat="40.045124" lon="116.306084"><ele>52.08</ele> +<time>2024-11-11T11:46:21Z</time> +</trkpt> +<trkpt lat="40.045074" lon="116.306114"><ele>52.00</ele> +<time>2024-11-11T11:46:22Z</time> +</trkpt> +<trkpt lat="40.045025" lon="116.306152"><ele>52.00</ele> +<time>2024-11-11T11:46:23Z</time> +</trkpt> +<trkpt lat="40.044975" lon="116.306183"><ele>52.09</ele> +<time>2024-11-11T11:46:24Z</time> +</trkpt> +<trkpt lat="40.044930" lon="116.306221"><ele>52.10</ele> +<time>2024-11-11T11:46:25Z</time> +</trkpt> +<trkpt lat="40.044880" lon="116.306259"><ele>52.09</ele> +<time>2024-11-11T11:46:26Z</time> +</trkpt> +<trkpt lat="40.044830" lon="116.306290"><ele>52.00</ele> +<time>2024-11-11T11:46:27Z</time> +</trkpt> +<trkpt lat="40.044785" lon="116.306328"><ele>51.97</ele> +<time>2024-11-11T11:46:28Z</time> +</trkpt> +<trkpt lat="40.044739" lon="116.306366"><ele>52.00</ele> +<time>2024-11-11T11:46:29Z</time> +</trkpt> +<trkpt lat="40.044689" lon="116.306412"><ele>52.12</ele> +<time>2024-11-11T11:46:30Z</time> +</trkpt> +<trkpt lat="40.044643" lon="116.306450"><ele>52.12</ele> +<time>2024-11-11T11:46:31Z</time> +</trkpt> +<trkpt lat="40.044594" lon="116.306488"><ele>52.24</ele> +<time>2024-11-11T11:46:32Z</time> +</trkpt> +<trkpt lat="40.044548" lon="116.306534"><ele>52.42</ele> +<time>2024-11-11T11:46:33Z</time> +</trkpt> +<trkpt lat="40.044498" lon="116.306564"><ele>52.42</ele> +<time>2024-11-11T11:46:34Z</time> +</trkpt> +<trkpt lat="40.044449" lon="116.306602"><ele>52.25</ele> +<time>2024-11-11T11:46:35Z</time> +</trkpt> +<trkpt lat="40.044399" lon="116.306641"><ele>52.24</ele> +<time>2024-11-11T11:46:36Z</time> +</trkpt> +<trkpt lat="40.044350" lon="116.306679"><ele>52.24</ele> +<time>2024-11-11T11:46:37Z</time> +</trkpt> +<trkpt lat="40.044300" lon="116.306709"><ele>52.24</ele> +<time>2024-11-11T11:46:38Z</time> +</trkpt> +<trkpt lat="40.044254" lon="116.306747"><ele>52.18</ele> +<time>2024-11-11T11:46:39Z</time> +</trkpt> +<trkpt lat="40.044205" lon="116.306786"><ele>52.18</ele> +<time>2024-11-11T11:46:40Z</time> +</trkpt> +<trkpt lat="40.044155" lon="116.306824"><ele>52.14</ele> +<time>2024-11-11T11:46:41Z</time> +</trkpt> +<trkpt lat="40.044106" lon="116.306854"><ele>52.14</ele> +<time>2024-11-11T11:46:42Z</time> +</trkpt> +<trkpt lat="40.044056" lon="116.306892"><ele>52.11</ele> +<time>2024-11-11T11:46:43Z</time> +</trkpt> +<trkpt lat="40.044006" lon="116.306931"><ele>52.34</ele> +<time>2024-11-11T11:46:44Z</time> +</trkpt> +<trkpt lat="40.043957" lon="116.306969"><ele>52.34</ele> +<time>2024-11-11T11:46:45Z</time> +</trkpt> +<trkpt lat="40.043907" lon="116.307007"><ele>52.44</ele> +<time>2024-11-11T11:46:46Z</time> +</trkpt> +<trkpt lat="40.043858" lon="116.307037"><ele>52.44</ele> +<time>2024-11-11T11:46:47Z</time> +</trkpt> +<trkpt lat="40.043804" lon="116.307068"><ele>52.15</ele> +<time>2024-11-11T11:46:48Z</time> +</trkpt> +<trkpt lat="40.043755" lon="116.307098"><ele>52.10</ele> +<time>2024-11-11T11:46:49Z</time> +</trkpt> +<trkpt lat="40.043701" lon="116.307129"><ele>52.12</ele> +<time>2024-11-11T11:46:50Z</time> +</trkpt> +<trkpt lat="40.043652" lon="116.307159"><ele>52.12</ele> +<time>2024-11-11T11:46:51Z</time> +</trkpt> +<trkpt lat="40.043598" lon="116.307190"><ele>52.11</ele> +<time>2024-11-11T11:46:52Z</time> +</trkpt> +<trkpt lat="40.043552" lon="116.307228"><ele>51.97</ele> +<time>2024-11-11T11:46:53Z</time> +</trkpt> +<trkpt lat="40.043503" lon="116.307259"><ele>51.97</ele> +<time>2024-11-11T11:46:54Z</time> +</trkpt> +<trkpt lat="40.043453" lon="116.307297"><ele>51.92</ele> +<time>2024-11-11T11:46:55Z</time> +</trkpt> +<trkpt lat="40.043404" lon="116.307343"><ele>52.24</ele> +<time>2024-11-11T11:46:56Z</time> +</trkpt> +<trkpt lat="40.043350" lon="116.307381"><ele>52.29</ele> +<time>2024-11-11T11:46:57Z</time> +</trkpt> +<trkpt lat="40.043304" lon="116.307411"><ele>52.47</ele> +<time>2024-11-11T11:46:58Z</time> +</trkpt> +<trkpt lat="40.043259" lon="116.307457"><ele>52.47</ele> +<time>2024-11-11T11:46:59Z</time> +</trkpt> +<trkpt lat="40.043217" lon="116.307495"><ele>52.45</ele> +<time>2024-11-11T11:47:00Z</time> +</trkpt> +<trkpt lat="40.043171" lon="116.307541"><ele>52.45</ele> +<time>2024-11-11T11:47:01Z</time> +</trkpt> +<trkpt lat="40.043133" lon="116.307587"><ele>52.42</ele> +<time>2024-11-11T11:47:02Z</time> +</trkpt> +<trkpt lat="40.043095" lon="116.307625"><ele>52.57</ele> +<time>2024-11-11T11:47:03Z</time> +</trkpt> +<trkpt lat="40.043060" lon="116.307671"><ele>52.47</ele> +<time>2024-11-11T11:47:04Z</time> +</trkpt> +<trkpt lat="40.043026" lon="116.307716"><ele>52.61</ele> +<time>2024-11-11T11:47:05Z</time> +</trkpt> +<trkpt lat="40.042999" lon="116.307770"><ele>52.61</ele> +<time>2024-11-11T11:47:06Z</time> +</trkpt> +<trkpt lat="40.042984" lon="116.307816"><ele>52.78</ele> +<time>2024-11-11T11:47:07Z</time> +</trkpt> +<trkpt lat="40.042969" lon="116.307884"><ele>52.89</ele> +<time>2024-11-11T11:47:08Z</time> +</trkpt> +<trkpt lat="40.042961" lon="116.307953"><ele>52.89</ele> +<time>2024-11-11T11:47:09Z</time> +</trkpt> +<trkpt lat="40.042957" lon="116.308022"><ele>52.78</ele> +<time>2024-11-11T11:47:10Z</time> +</trkpt> +<trkpt lat="40.042957" lon="116.308090"><ele>52.55</ele> +<time>2024-11-11T11:47:11Z</time> +</trkpt> +<trkpt lat="40.042961" lon="116.308151"><ele>52.51</ele> +<time>2024-11-11T11:47:12Z</time> +</trkpt> +<trkpt lat="40.042969" lon="116.308189"><ele>52.00</ele> +<time>2024-11-11T11:47:13Z</time> +</trkpt> +<trkpt lat="40.042992" lon="116.308243"><ele>51.93</ele> +<time>2024-11-11T11:47:14Z</time> +</trkpt> +<trkpt lat="40.043022" lon="116.308304"><ele>51.91</ele> +<time>2024-11-11T11:47:15Z</time> +</trkpt> +<trkpt lat="40.043049" lon="116.308365"><ele>51.66</ele> +<time>2024-11-11T11:47:16Z</time> +</trkpt> +<trkpt lat="40.043083" lon="116.308418"><ele>51.47</ele> +<time>2024-11-11T11:47:17Z</time> +</trkpt> +<trkpt lat="40.043114" lon="116.308479"><ele>51.36</ele> +<time>2024-11-11T11:47:18Z</time> +</trkpt> +<trkpt lat="40.043140" lon="116.308533"><ele>51.27</ele> +<time>2024-11-11T11:47:19Z</time> +</trkpt> +<trkpt lat="40.043171" lon="116.308586"><ele>51.27</ele> +<time>2024-11-11T11:47:20Z</time> +</trkpt> +<trkpt lat="40.043198" lon="116.308632"><ele>51.23</ele> +<time>2024-11-11T11:47:21Z</time> +</trkpt> +<trkpt lat="40.043232" lon="116.308685"><ele>51.23</ele> +<time>2024-11-11T11:47:22Z</time> +</trkpt> +<trkpt lat="40.043270" lon="116.308739"><ele>51.10</ele> +<time>2024-11-11T11:47:23Z</time> +</trkpt> +<trkpt lat="40.043304" lon="116.308784"><ele>51.04</ele> +<time>2024-11-11T11:47:24Z</time> +</trkpt> +<trkpt lat="40.043339" lon="116.308838"><ele>50.93</ele> +<time>2024-11-11T11:47:25Z</time> +</trkpt> +<trkpt lat="40.043377" lon="116.308884"><ele>50.93</ele> +<time>2024-11-11T11:47:26Z</time> +</trkpt> +<trkpt lat="40.043411" lon="116.308937"><ele>50.84</ele> +<time>2024-11-11T11:47:27Z</time> +</trkpt> +<trkpt lat="40.043449" lon="116.308990"><ele>50.74</ele> +<time>2024-11-11T11:47:28Z</time> +</trkpt> +<trkpt lat="40.043484" lon="116.309044"><ele>50.84</ele> +<time>2024-11-11T11:47:29Z</time> +</trkpt> +<trkpt lat="40.043518" lon="116.309097"><ele>50.86</ele> +<time>2024-11-11T11:47:30Z</time> +</trkpt> +<trkpt lat="40.043556" lon="116.309151"><ele>50.86</ele> +<time>2024-11-11T11:47:31Z</time> +</trkpt> +<trkpt lat="40.043591" lon="116.309204"><ele>50.87</ele> +<time>2024-11-11T11:47:32Z</time> +</trkpt> +<trkpt lat="40.043625" lon="116.309265"><ele>50.86</ele> +<time>2024-11-11T11:47:33Z</time> +</trkpt> +<trkpt lat="40.043659" lon="116.309319"><ele>50.76</ele> +<time>2024-11-11T11:47:34Z</time> +</trkpt> +<trkpt lat="40.043697" lon="116.309372"><ele>50.73</ele> +<time>2024-11-11T11:47:35Z</time> +</trkpt> +<trkpt lat="40.043736" lon="116.309425"><ele>50.73</ele> +<time>2024-11-11T11:47:36Z</time> +</trkpt> +<trkpt lat="40.043774" lon="116.309479"><ele>51.07</ele> +<time>2024-11-11T11:47:37Z</time> +</trkpt> +<trkpt lat="40.043808" lon="116.309532"><ele>51.19</ele> +<time>2024-11-11T11:47:38Z</time> +</trkpt> +<trkpt lat="40.043842" lon="116.309586"><ele>51.38</ele> +<time>2024-11-11T11:47:39Z</time> +</trkpt> +<trkpt lat="40.043877" lon="116.309639"><ele>51.48</ele> +<time>2024-11-11T11:47:40Z</time> +</trkpt> +<trkpt lat="40.043915" lon="116.309700"><ele>51.51</ele> +<time>2024-11-11T11:47:41Z</time> +</trkpt> +<trkpt lat="40.043949" lon="116.309753"><ele>51.67</ele> +<time>2024-11-11T11:47:42Z</time> +</trkpt> +<trkpt lat="40.043987" lon="116.309807"><ele>51.67</ele> +<time>2024-11-11T11:47:43Z</time> +</trkpt> +<trkpt lat="40.044022" lon="116.309860"><ele>51.70</ele> +<time>2024-11-11T11:47:44Z</time> +</trkpt> +<trkpt lat="40.044060" lon="116.309921"><ele>51.89</ele> +<time>2024-11-11T11:47:45Z</time> +</trkpt> +<trkpt lat="40.044098" lon="116.309982"><ele>52.13</ele> +<time>2024-11-11T11:47:46Z</time> +</trkpt> +<trkpt lat="40.044132" lon="116.310036"><ele>51.97</ele> +<time>2024-11-11T11:47:47Z</time> +</trkpt> +<trkpt lat="40.044167" lon="116.310089"><ele>51.97</ele> +<time>2024-11-11T11:47:48Z</time> +</trkpt> +<trkpt lat="40.044205" lon="116.310150"><ele>52.14</ele> +<time>2024-11-11T11:47:49Z</time> +</trkpt> +<trkpt lat="40.044239" lon="116.310204"><ele>51.96</ele> +<time>2024-11-11T11:47:50Z</time> +</trkpt> +<trkpt lat="40.044273" lon="116.310257"><ele>51.96</ele> +<time>2024-11-11T11:47:51Z</time> +</trkpt> +<trkpt lat="40.044308" lon="116.310310"><ele>51.96</ele> +<time>2024-11-11T11:47:52Z</time> +</trkpt> +<trkpt lat="40.044342" lon="116.310371"><ele>51.94</ele> +<time>2024-11-11T11:47:53Z</time> +</trkpt> +<trkpt lat="40.044376" lon="116.310425"><ele>51.98</ele> +<time>2024-11-11T11:47:54Z</time> +</trkpt> +<trkpt lat="40.044407" lon="116.310478"><ele>52.00</ele> +<time>2024-11-11T11:47:55Z</time> +</trkpt> +<trkpt lat="40.044441" lon="116.310539"><ele>52.02</ele> +<time>2024-11-11T11:47:56Z</time> +</trkpt> +<trkpt lat="40.044479" lon="116.310600"><ele>52.02</ele> +<time>2024-11-11T11:47:57Z</time> +</trkpt> +<trkpt lat="40.044510" lon="116.310654"><ele>51.96</ele> +<time>2024-11-11T11:47:58Z</time> +</trkpt> +<trkpt lat="40.044540" lon="116.310715"><ele>51.89</ele> +<time>2024-11-11T11:47:59Z</time> +</trkpt> +<trkpt lat="40.044567" lon="116.310776"><ele>51.85</ele> +<time>2024-11-11T11:48:00Z</time> +</trkpt> +<trkpt lat="40.044598" lon="116.310837"><ele>51.85</ele> +<time>2024-11-11T11:48:01Z</time> +</trkpt> +<trkpt lat="40.044628" lon="116.310898"><ele>51.72</ele> +<time>2024-11-11T11:48:02Z</time> +</trkpt> +<trkpt lat="40.044655" lon="116.310959"><ele>51.63</ele> +<time>2024-11-11T11:48:03Z</time> +</trkpt> +<trkpt lat="40.044682" lon="116.311020"><ele>51.63</ele> +<time>2024-11-11T11:48:04Z</time> +</trkpt> +<trkpt lat="40.044712" lon="116.311073"><ele>51.63</ele> +<time>2024-11-11T11:48:05Z</time> +</trkpt> +<trkpt lat="40.044746" lon="116.311134"><ele>51.16</ele> +<time>2024-11-11T11:48:06Z</time> +</trkpt> +<trkpt lat="40.044781" lon="116.311188"><ele>51.16</ele> +<time>2024-11-11T11:48:07Z</time> +</trkpt> +<trkpt lat="40.044811" lon="116.311241"><ele>51.33</ele> +<time>2024-11-11T11:48:08Z</time> +</trkpt> +<trkpt lat="40.044853" lon="116.311302"><ele>51.37</ele> +<time>2024-11-11T11:48:09Z</time> +</trkpt> +<trkpt lat="40.044888" lon="116.311356"><ele>51.37</ele> +<time>2024-11-11T11:48:10Z</time> +</trkpt> +<trkpt lat="40.044926" lon="116.311424"><ele>51.42</ele> +<time>2024-11-11T11:48:11Z</time> +</trkpt> +<trkpt lat="40.044960" lon="116.311478"><ele>51.59</ele> +<time>2024-11-11T11:48:12Z</time> +</trkpt> +<trkpt lat="40.044998" lon="116.311539"><ele>51.66</ele> +<time>2024-11-11T11:48:13Z</time> +</trkpt> +<trkpt lat="40.045033" lon="116.311592"><ele>51.74</ele> +<time>2024-11-11T11:48:14Z</time> +</trkpt> +<trkpt lat="40.045063" lon="116.311638"><ele>51.74</ele> +<time>2024-11-11T11:48:15Z</time> +</trkpt> +<trkpt lat="40.045101" lon="116.311691"><ele>51.72</ele> +<time>2024-11-11T11:48:16Z</time> +</trkpt> +<trkpt lat="40.045135" lon="116.311745"><ele>51.72</ele> +<time>2024-11-11T11:48:17Z</time> +</trkpt> +<trkpt lat="40.045166" lon="116.311798"><ele>51.83</ele> +<time>2024-11-11T11:48:18Z</time> +</trkpt> +<trkpt lat="40.045204" lon="116.311852"><ele>51.87</ele> +<time>2024-11-11T11:48:19Z</time> +</trkpt> +<trkpt lat="40.045246" lon="116.311913"><ele>51.83</ele> +<time>2024-11-11T11:48:20Z</time> +</trkpt> +<trkpt lat="40.045284" lon="116.311966"><ele>51.75</ele> +<time>2024-11-11T11:48:21Z</time> +</trkpt> +<trkpt lat="40.045326" lon="116.312035"><ele>51.57</ele> +<time>2024-11-11T11:48:22Z</time> +</trkpt> +<trkpt lat="40.045361" lon="116.312088"><ele>51.51</ele> +<time>2024-11-11T11:48:23Z</time> +</trkpt> +<trkpt lat="40.045391" lon="116.312141"><ele>51.57</ele> +<time>2024-11-11T11:48:24Z</time> +</trkpt> +<trkpt lat="40.045425" lon="116.312195"><ele>51.53</ele> +<time>2024-11-11T11:48:25Z</time> +</trkpt> +<trkpt lat="40.045456" lon="116.312241"><ele>51.79</ele> +<time>2024-11-11T11:48:26Z</time> +</trkpt> +<trkpt lat="40.045490" lon="116.312286"><ele>51.79</ele> +<time>2024-11-11T11:48:27Z</time> +</trkpt> +<trkpt lat="40.045525" lon="116.312332"><ele>51.84</ele> +<time>2024-11-11T11:48:28Z</time> +</trkpt> +<trkpt lat="40.045559" lon="116.312378"><ele>52.17</ele> +<time>2024-11-11T11:48:29Z</time> +</trkpt> +<trkpt lat="40.045601" lon="116.312431"><ele>52.29</ele> +<time>2024-11-11T11:48:30Z</time> +</trkpt> +<trkpt lat="40.045639" lon="116.312477"><ele>52.29</ele> +<time>2024-11-11T11:48:31Z</time> +</trkpt> +<trkpt lat="40.045670" lon="116.312515"><ele>52.30</ele> +<time>2024-11-11T11:48:32Z</time> +</trkpt> +<trkpt lat="40.045700" lon="116.312561"><ele>52.44</ele> +<time>2024-11-11T11:48:33Z</time> +</trkpt> +<trkpt lat="40.045734" lon="116.312607"><ele>52.30</ele> +<time>2024-11-11T11:48:34Z</time> +</trkpt> +<trkpt lat="40.045769" lon="116.312653"><ele>52.44</ele> +<time>2024-11-11T11:48:35Z</time> +</trkpt> +<trkpt lat="40.045795" lon="116.312698"><ele>52.71</ele> +<time>2024-11-11T11:48:36Z</time> +</trkpt> +<trkpt lat="40.045826" lon="116.312737"><ele>52.68</ele> +<time>2024-11-11T11:48:37Z</time> +</trkpt> +<trkpt lat="40.045856" lon="116.312782"><ele>52.69</ele> +<time>2024-11-11T11:48:38Z</time> +</trkpt> +<trkpt lat="40.045872" lon="116.312790"><ele>52.69</ele> +<time>2024-11-11T11:48:39Z</time> +</trkpt> +<trkpt lat="40.045918" lon="116.312843"><ele>52.86</ele> +<time>2024-11-11T11:48:40Z</time> +</trkpt> +<trkpt lat="40.045956" lon="116.312889"><ele>52.92</ele> +<time>2024-11-11T11:48:41Z</time> +</trkpt> +<trkpt lat="40.045994" lon="116.312920"><ele>52.86</ele> +<time>2024-11-11T11:48:42Z</time> +</trkpt> +<trkpt lat="40.046028" lon="116.312958"><ele>52.84</ele> +<time>2024-11-11T11:48:43Z</time> +</trkpt> +<trkpt lat="40.046055" lon="116.313004"><ele>52.83</ele> +<time>2024-11-11T11:48:44Z</time> +</trkpt> +<trkpt lat="40.046074" lon="116.313049"><ele>52.91</ele> +<time>2024-11-11T11:48:45Z</time> +</trkpt> +<trkpt lat="40.046085" lon="116.313072"><ele>52.99</ele> +<time>2024-11-11T11:48:46Z</time> +</trkpt> +<trkpt lat="40.046104" lon="116.313087"><ele>53.02</ele> +<time>2024-11-11T11:48:47Z</time> +</trkpt> +<trkpt lat="40.046127" lon="116.313118"><ele>53.02</ele> +<time>2024-11-11T11:48:48Z</time> +</trkpt> +<trkpt lat="40.046139" lon="116.313133"><ele>52.93</ele> +<time>2024-11-11T11:48:49Z</time> +</trkpt> +<trkpt lat="40.046146" lon="116.313156"><ele>52.93</ele> +<time>2024-11-11T11:48:50Z</time> +</trkpt> +<trkpt lat="40.046154" lon="116.313171"><ele>52.93</ele> +<time>2024-11-11T11:48:51Z</time> +</trkpt> +<trkpt lat="40.046162" lon="116.313194"><ele>52.95</ele> +<time>2024-11-11T11:48:52Z</time> +</trkpt> +<trkpt lat="40.046165" lon="116.313202"><ele>53.06</ele> +<time>2024-11-11T11:48:53Z</time> +</trkpt> +<trkpt lat="40.046165" lon="116.313202"><ele>53.06</ele> +<time>2024-11-11T11:48:54Z</time> +</trkpt> +<trkpt lat="40.046169" lon="116.313202"><ele>53.07</ele> +<time>2024-11-11T11:48:55Z</time> +</trkpt> +<trkpt lat="40.046169" lon="116.313202"><ele>53.01</ele> +<time>2024-11-11T11:48:56Z</time> +</trkpt> +<trkpt lat="40.046173" lon="116.313210"><ele>53.09</ele> +<time>2024-11-11T11:48:57Z</time> +</trkpt> +<trkpt lat="40.046173" lon="116.313210"><ele>53.09</ele> +<time>2024-11-11T11:48:58Z</time> +</trkpt> +<trkpt lat="40.046173" lon="116.313210"><ele>53.17</ele> +<time>2024-11-11T11:48:59Z</time> +</trkpt> +<trkpt lat="40.046177" lon="116.313217"><ele>53.18</ele> +<time>2024-11-11T11:49:00Z</time> +</trkpt> +<trkpt lat="40.046177" lon="116.313217"><ele>53.18</ele> +<time>2024-11-11T11:49:01Z</time> +</trkpt> +<trkpt lat="40.046181" lon="116.313225"><ele>53.17</ele> +<time>2024-11-11T11:49:02Z</time> +</trkpt> +<trkpt lat="40.046181" lon="116.313225"><ele>53.17</ele> +<time>2024-11-11T11:49:03Z</time> +</trkpt> +<trkpt lat="40.046185" lon="116.313232"><ele>53.18</ele> +<time>2024-11-11T11:49:04Z</time> +</trkpt> +<trkpt lat="40.046185" lon="116.313232"><ele>53.16</ele> +<time>2024-11-11T11:49:05Z</time> +</trkpt> +<trkpt lat="40.046185" lon="116.313232"><ele>53.18</ele> +<time>2024-11-11T11:49:06Z</time> +</trkpt> +<trkpt lat="40.046188" lon="116.313240"><ele>53.23</ele> +<time>2024-11-11T11:49:07Z</time> +</trkpt> +<trkpt lat="40.046188" lon="116.313240"><ele>53.23</ele> +<time>2024-11-11T11:49:08Z</time> +</trkpt> +<trkpt lat="40.046188" lon="116.313240"><ele>53.10</ele> +<time>2024-11-11T11:49:09Z</time> +</trkpt> +<trkpt lat="40.046188" lon="116.313240"><ele>53.10</ele> +<time>2024-11-11T11:49:10Z</time> +</trkpt> +<trkpt lat="40.046188" lon="116.313240"><ele>52.91</ele> +<time>2024-11-11T11:49:11Z</time> +</trkpt> +<trkpt lat="40.046192" lon="116.313248"><ele>52.88</ele> +<time>2024-11-11T11:49:12Z</time> +</trkpt> +<trkpt lat="40.046192" lon="116.313248"><ele>52.88</ele> +<time>2024-11-11T11:49:13Z</time> +</trkpt> +<trkpt lat="40.046196" lon="116.313248"><ele>52.88</ele> +<time>2024-11-11T11:49:14Z</time> +</trkpt> +<trkpt lat="40.046200" lon="116.313248"><ele>52.88</ele> +<time>2024-11-11T11:49:15Z</time> +</trkpt> +<trkpt lat="40.046200" lon="116.313255"><ele>52.90</ele> +<time>2024-11-11T11:49:16Z</time> +</trkpt> +<trkpt lat="40.046200" lon="116.313255"><ele>52.90</ele> +<time>2024-11-11T11:49:17Z</time> +</trkpt> +<trkpt lat="40.046200" lon="116.313255"><ele>52.77</ele> +<time>2024-11-11T11:49:18Z</time> +</trkpt> +<trkpt lat="40.046200" lon="116.313255"><ele>52.90</ele> +<time>2024-11-11T11:49:19Z</time> +</trkpt> +<trkpt lat="40.046204" lon="116.313255"><ele>52.83</ele> +<time>2024-11-11T11:49:20Z</time> +</trkpt> +<trkpt lat="40.046211" lon="116.313263"><ele>52.90</ele> +<time>2024-11-11T11:49:21Z</time> +</trkpt> +<trkpt lat="40.046219" lon="116.313286"><ele>52.90</ele> +<time>2024-11-11T11:49:22Z</time> +</trkpt> +<trkpt lat="40.046234" lon="116.313324"><ele>52.85</ele> +<time>2024-11-11T11:49:23Z</time> +</trkpt> +<trkpt lat="40.046249" lon="116.313354"><ele>52.85</ele> +<time>2024-11-11T11:49:24Z</time> +</trkpt> +<trkpt lat="40.046268" lon="116.313393"><ele>52.67</ele> +<time>2024-11-11T11:49:25Z</time> +</trkpt> +<trkpt lat="40.046288" lon="116.313431"><ele>52.61</ele> +<time>2024-11-11T11:49:26Z</time> +</trkpt> +<trkpt lat="40.046303" lon="116.313469"><ele>52.60</ele> +<time>2024-11-11T11:49:27Z</time> +</trkpt> +<trkpt lat="40.046318" lon="116.313507"><ele>52.59</ele> +<time>2024-11-11T11:49:28Z</time> +</trkpt> +<trkpt lat="40.046337" lon="116.313530"><ele>52.59</ele> +<time>2024-11-11T11:49:29Z</time> +</trkpt> +<trkpt lat="40.046352" lon="116.313545"><ele>52.59</ele> +<time>2024-11-11T11:49:30Z</time> +</trkpt> +<trkpt lat="40.046356" lon="116.313545"><ele>52.48</ele> +<time>2024-11-11T11:49:31Z</time> +</trkpt> +<trkpt lat="40.046360" lon="116.313545"><ele>52.48</ele> +<time>2024-11-11T11:49:32Z</time> +</trkpt> +<trkpt lat="40.046360" lon="116.313545"><ele>52.64</ele> +<time>2024-11-11T11:49:33Z</time> +</trkpt> +<trkpt lat="40.046364" lon="116.313545"><ele>52.74</ele> +<time>2024-11-11T11:49:34Z</time> +</trkpt> +<trkpt lat="40.046368" lon="116.313545"><ele>52.73</ele> +<time>2024-11-11T11:49:35Z</time> +</trkpt> +<trkpt lat="40.046368" lon="116.313545"><ele>52.73</ele> +<time>2024-11-11T11:49:36Z</time> +</trkpt> +<trkpt lat="40.046371" lon="116.313553"><ele>52.73</ele> +<time>2024-11-11T11:49:37Z</time> +</trkpt> +<trkpt lat="40.046371" lon="116.313553"><ele>52.73</ele> +<time>2024-11-11T11:49:38Z</time> +</trkpt> +<trkpt lat="40.046375" lon="116.313553"><ele>52.71</ele> +<time>2024-11-11T11:49:39Z</time> +</trkpt> +<trkpt lat="40.046379" lon="116.313553"><ele>52.69</ele> +<time>2024-11-11T11:49:40Z</time> +</trkpt> +<trkpt lat="40.046383" lon="116.313553"><ele>52.69</ele> +<time>2024-11-11T11:49:41Z</time> +</trkpt> +<trkpt lat="40.046387" lon="116.313553"><ele>52.68</ele> +<time>2024-11-11T11:49:42Z</time> +</trkpt> +<trkpt lat="40.046387" lon="116.313553"><ele>52.75</ele> +<time>2024-11-11T11:49:43Z</time> +</trkpt> +<trkpt lat="40.046391" lon="116.313553"><ele>52.81</ele> +<time>2024-11-11T11:49:44Z</time> +</trkpt> +<trkpt lat="40.046394" lon="116.313553"><ele>52.81</ele> +<time>2024-11-11T11:49:45Z</time> +</trkpt> +<trkpt lat="40.046398" lon="116.313553"><ele>52.81</ele> +<time>2024-11-11T11:49:46Z</time> +</trkpt> +<trkpt lat="40.046402" lon="116.313553"><ele>52.84</ele> +<time>2024-11-11T11:49:47Z</time> +</trkpt> +<trkpt lat="40.046402" lon="116.313553"><ele>52.84</ele> +<time>2024-11-11T11:49:48Z</time> +</trkpt> +<trkpt lat="40.046406" lon="116.313553"><ele>52.84</ele> +<time>2024-11-11T11:49:49Z</time> +</trkpt> +<trkpt lat="40.046406" lon="116.313553"><ele>52.81</ele> +<time>2024-11-11T11:49:50Z</time> +</trkpt> +<trkpt lat="40.046410" lon="116.313553"><ele>52.84</ele> +<time>2024-11-11T11:49:51Z</time> +</trkpt> +<trkpt lat="40.046410" lon="116.313553"><ele>52.90</ele> +<time>2024-11-11T11:49:52Z</time> +</trkpt> +<trkpt lat="40.046413" lon="116.313553"><ele>52.90</ele> +<time>2024-11-11T11:49:53Z</time> +</trkpt> +<trkpt lat="40.046413" lon="116.313553"><ele>52.90</ele> +<time>2024-11-11T11:49:54Z</time> +</trkpt> +<trkpt lat="40.046417" lon="116.313553"><ele>52.90</ele> +<time>2024-11-11T11:49:55Z</time> +</trkpt> +<trkpt lat="40.046417" lon="116.313553"><ele>52.91</ele> +<time>2024-11-11T11:49:56Z</time> +</trkpt> +<trkpt lat="40.046417" lon="116.313553"><ele>52.85</ele> +<time>2024-11-11T11:49:57Z</time> +</trkpt> +<trkpt lat="40.046421" lon="116.313553"><ele>52.84</ele> +<time>2024-11-11T11:49:58Z</time> +</trkpt> +<trkpt lat="40.046421" lon="116.313553"><ele>52.84</ele> +<time>2024-11-11T11:49:59Z</time> +</trkpt> +<trkpt lat="40.046421" lon="116.313553"><ele>52.93</ele> +<time>2024-11-11T11:50:00Z</time> +</trkpt> +<trkpt lat="40.046425" lon="116.313553"><ele>52.84</ele> +<time>2024-11-11T11:50:01Z</time> +</trkpt> +<trkpt lat="40.046436" lon="116.313553"><ele>52.84</ele> +<time>2024-11-11T11:50:02Z</time> +</trkpt> +<trkpt lat="40.046452" lon="116.313538"><ele>52.84</ele> +<time>2024-11-11T11:50:03Z</time> +</trkpt> +<trkpt lat="40.046474" lon="116.313522"><ele>52.72</ele> +<time>2024-11-11T11:50:04Z</time> +</trkpt> +<trkpt lat="40.046497" lon="116.313507"><ele>52.72</ele> +<time>2024-11-11T11:50:05Z</time> +</trkpt> +<trkpt lat="40.046520" lon="116.313484"><ele>52.72</ele> +<time>2024-11-11T11:50:06Z</time> +</trkpt> +<trkpt lat="40.046551" lon="116.313461"><ele>52.65</ele> +<time>2024-11-11T11:50:07Z</time> +</trkpt> +<trkpt lat="40.046589" lon="116.313438"><ele>52.37</ele> +<time>2024-11-11T11:50:08Z</time> +</trkpt> +<trkpt lat="40.046627" lon="116.313416"><ele>52.29</ele> +<time>2024-11-11T11:50:09Z</time> +</trkpt> +<trkpt lat="40.046665" lon="116.313393"><ele>52.29</ele> +<time>2024-11-11T11:50:10Z</time> +</trkpt> +<trkpt lat="40.046703" lon="116.313370"><ele>52.29</ele> +<time>2024-11-11T11:50:11Z</time> +</trkpt> +<trkpt lat="40.046738" lon="116.313347"><ele>52.29</ele> +<time>2024-11-11T11:50:12Z</time> +</trkpt> +<trkpt lat="40.046772" lon="116.313332"><ele>52.41</ele> +<time>2024-11-11T11:50:13Z</time> +</trkpt> +<trkpt lat="40.046810" lon="116.313316"><ele>52.54</ele> +<time>2024-11-11T11:50:14Z</time> +</trkpt> +<trkpt lat="40.046852" lon="116.313301"><ele>52.54</ele> +<time>2024-11-11T11:50:15Z</time> +</trkpt> +<trkpt lat="40.046898" lon="116.313286"><ele>52.54</ele> +<time>2024-11-11T11:50:16Z</time> +</trkpt> +<trkpt lat="40.046944" lon="116.313271"><ele>52.54</ele> +<time>2024-11-11T11:50:17Z</time> +</trkpt> +<trkpt lat="40.046989" lon="116.313248"><ele>52.54</ele> +<time>2024-11-11T11:50:18Z</time> +</trkpt> +<trkpt lat="40.047035" lon="116.313232"><ele>52.54</ele> +<time>2024-11-11T11:50:19Z</time> +</trkpt> +<trkpt lat="40.047073" lon="116.313217"><ele>52.62</ele> +<time>2024-11-11T11:50:20Z</time> +</trkpt> +<trkpt lat="40.047115" lon="116.313202"><ele>52.86</ele> +<time>2024-11-11T11:50:21Z</time> +</trkpt> +<trkpt lat="40.047157" lon="116.313194"><ele>52.86</ele> +<time>2024-11-11T11:50:22Z</time> +</trkpt> +<trkpt lat="40.047199" lon="116.313179"><ele>52.86</ele> +<time>2024-11-11T11:50:23Z</time> +</trkpt> +<trkpt lat="40.047245" lon="116.313164"><ele>52.86</ele> +<time>2024-11-11T11:50:24Z</time> +</trkpt> +<trkpt lat="40.047287" lon="116.313148"><ele>53.08</ele> +<time>2024-11-11T11:50:25Z</time> +</trkpt> +<trkpt lat="40.047325" lon="116.313133"><ele>53.08</ele> +<time>2024-11-11T11:50:26Z</time> +</trkpt> +<trkpt lat="40.047367" lon="116.313126"><ele>53.08</ele> +<time>2024-11-11T11:50:27Z</time> +</trkpt> +<trkpt lat="40.047409" lon="116.313110"><ele>52.89</ele> +<time>2024-11-11T11:50:28Z</time> +</trkpt> +<trkpt lat="40.047451" lon="116.313095"><ele>52.75</ele> +<time>2024-11-11T11:50:29Z</time> +</trkpt> +<trkpt lat="40.047497" lon="116.313080"><ele>52.63</ele> +<time>2024-11-11T11:50:30Z</time> +</trkpt> +<trkpt lat="40.047546" lon="116.313065"><ele>52.21</ele> +<time>2024-11-11T11:50:31Z</time> +</trkpt> +<trkpt lat="40.047592" lon="116.313049"><ele>52.21</ele> +<time>2024-11-11T11:50:32Z</time> +</trkpt> +<trkpt lat="40.047638" lon="116.313034"><ele>52.51</ele> +<time>2024-11-11T11:50:33Z</time> +</trkpt> +<trkpt lat="40.047684" lon="116.313019"><ele>52.52</ele> +<time>2024-11-11T11:50:34Z</time> +</trkpt> +<trkpt lat="40.047729" lon="116.313004"><ele>52.52</ele> +<time>2024-11-11T11:50:35Z</time> +</trkpt> +<trkpt lat="40.047771" lon="116.312981"><ele>52.55</ele> +<time>2024-11-11T11:50:36Z</time> +</trkpt> +<trkpt lat="40.047817" lon="116.312965"><ele>52.85</ele> +<time>2024-11-11T11:50:37Z</time> +</trkpt> +<trkpt lat="40.047863" lon="116.312950"><ele>52.85</ele> +<time>2024-11-11T11:50:38Z</time> +</trkpt> +<trkpt lat="40.047905" lon="116.312935"><ele>52.65</ele> +<time>2024-11-11T11:50:39Z</time> +</trkpt> +<trkpt lat="40.047947" lon="116.312920"><ele>52.64</ele> +<time>2024-11-11T11:50:40Z</time> +</trkpt> +<trkpt lat="40.047989" lon="116.312897"><ele>52.64</ele> +<time>2024-11-11T11:50:41Z</time> +</trkpt> +<trkpt lat="40.048031" lon="116.312881"><ele>52.84</ele> +<time>2024-11-11T11:50:42Z</time> +</trkpt> +<trkpt lat="40.048069" lon="116.312866"><ele>52.84</ele> +<time>2024-11-11T11:50:43Z</time> +</trkpt> +<trkpt lat="40.048107" lon="116.312859"><ele>52.84</ele> +<time>2024-11-11T11:50:44Z</time> +</trkpt> +<trkpt lat="40.048145" lon="116.312843"><ele>52.99</ele> +<time>2024-11-11T11:50:45Z</time> +</trkpt> +<trkpt lat="40.048187" lon="116.312836"><ele>52.99</ele> +<time>2024-11-11T11:50:46Z</time> +</trkpt> +<trkpt lat="40.048225" lon="116.312828"><ele>52.99</ele> +<time>2024-11-11T11:50:47Z</time> +</trkpt> +<trkpt lat="40.048267" lon="116.312828"><ele>52.79</ele> +<time>2024-11-11T11:50:48Z</time> +</trkpt> +<trkpt lat="40.048306" lon="116.312820"><ele>52.79</ele> +<time>2024-11-11T11:50:49Z</time> +</trkpt> +<trkpt lat="40.048340" lon="116.312813"><ele>52.95</ele> +<time>2024-11-11T11:50:50Z</time> +</trkpt> +<trkpt lat="40.048374" lon="116.312805"><ele>53.06</ele> +<time>2024-11-11T11:50:51Z</time> +</trkpt> +<trkpt lat="40.048401" lon="116.312805"><ele>53.15</ele> +<time>2024-11-11T11:50:52Z</time> +</trkpt> +<trkpt lat="40.048428" lon="116.312790"><ele>53.15</ele> +<time>2024-11-11T11:50:53Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312782"><ele>53.15</ele> +<time>2024-11-11T11:50:54Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312767"><ele>53.15</ele> +<time>2024-11-11T11:50:55Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312767"><ele>53.12</ele> +<time>2024-11-11T11:50:56Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312767"><ele>53.12</ele> +<time>2024-11-11T11:50:57Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312767"><ele>53.19</ele> +<time>2024-11-11T11:50:58Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312767"><ele>53.20</ele> +<time>2024-11-11T11:50:59Z</time> +</trkpt> +<trkpt lat="40.048447" lon="116.312759"><ele>53.25</ele> +<time>2024-11-11T11:51:00Z</time> +</trkpt> +<trkpt lat="40.048447" lon="116.312759"><ele>53.26</ele> +<time>2024-11-11T11:51:01Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312759"><ele>53.26</ele> +<time>2024-11-11T11:51:02Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312759"><ele>53.26</ele> +<time>2024-11-11T11:51:03Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312759"><ele>53.10</ele> +<time>2024-11-11T11:51:04Z</time> +</trkpt> +<trkpt lat="40.048439" lon="116.312752"><ele>53.10</ele> +<time>2024-11-11T11:51:05Z</time> +</trkpt> +<trkpt lat="40.048439" lon="116.312752"><ele>53.10</ele> +<time>2024-11-11T11:51:06Z</time> +</trkpt> +<trkpt lat="40.048439" lon="116.312752"><ele>53.20</ele> +<time>2024-11-11T11:51:07Z</time> +</trkpt> +<trkpt lat="40.048439" lon="116.312752"><ele>53.13</ele> +<time>2024-11-11T11:51:08Z</time> +</trkpt> +<trkpt lat="40.048439" lon="116.312752"><ele>53.20</ele> +<time>2024-11-11T11:51:09Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312752"><ele>53.26</ele> +<time>2024-11-11T11:51:10Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312752"><ele>53.26</ele> +<time>2024-11-11T11:51:11Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312744"><ele>53.13</ele> +<time>2024-11-11T11:51:12Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312744"><ele>53.10</ele> +<time>2024-11-11T11:51:13Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312744"><ele>53.10</ele> +<time>2024-11-11T11:51:14Z</time> +</trkpt> +<trkpt lat="40.048439" lon="116.312737"><ele>53.10</ele> +<time>2024-11-11T11:51:15Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312737"><ele>53.06</ele> +<time>2024-11-11T11:51:16Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312737"><ele>53.14</ele> +<time>2024-11-11T11:51:17Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312737"><ele>53.14</ele> +<time>2024-11-11T11:51:18Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312729"><ele>53.05</ele> +<time>2024-11-11T11:51:19Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312729"><ele>53.05</ele> +<time>2024-11-11T11:51:20Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312729"><ele>53.21</ele> +<time>2024-11-11T11:51:21Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312729"><ele>53.21</ele> +<time>2024-11-11T11:51:22Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312729"><ele>53.27</ele> +<time>2024-11-11T11:51:23Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312729"><ele>53.27</ele> +<time>2024-11-11T11:51:24Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312729"><ele>53.27</ele> +<time>2024-11-11T11:51:25Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312729"><ele>53.28</ele> +<time>2024-11-11T11:51:26Z</time> +</trkpt> +<trkpt lat="40.048447" lon="116.312729"><ele>53.28</ele> +<time>2024-11-11T11:51:27Z</time> +</trkpt> +<trkpt lat="40.048447" lon="116.312729"><ele>53.20</ele> +<time>2024-11-11T11:51:28Z</time> +</trkpt> +<trkpt lat="40.048447" lon="116.312729"><ele>53.20</ele> +<time>2024-11-11T11:51:29Z</time> +</trkpt> +<trkpt lat="40.048447" lon="116.312729"><ele>53.20</ele> +<time>2024-11-11T11:51:30Z</time> +</trkpt> +<trkpt lat="40.048447" lon="116.312729"><ele>53.17</ele> +<time>2024-11-11T11:51:31Z</time> +</trkpt> +<trkpt lat="40.048447" lon="116.312729"><ele>53.20</ele> +<time>2024-11-11T11:51:32Z</time> +</trkpt> +<trkpt lat="40.048447" lon="116.312721"><ele>53.20</ele> +<time>2024-11-11T11:51:33Z</time> +</trkpt> +<trkpt lat="40.048447" lon="116.312721"><ele>53.22</ele> +<time>2024-11-11T11:51:34Z</time> +</trkpt> +<trkpt lat="40.048447" lon="116.312721"><ele>53.28</ele> +<time>2024-11-11T11:51:35Z</time> +</trkpt> +<trkpt lat="40.048447" lon="116.312721"><ele>53.28</ele> +<time>2024-11-11T11:51:36Z</time> +</trkpt> +<trkpt lat="40.048447" lon="116.312721"><ele>53.22</ele> +<time>2024-11-11T11:51:37Z</time> +</trkpt> +<trkpt lat="40.048447" lon="116.312721"><ele>53.11</ele> +<time>2024-11-11T11:51:38Z</time> +</trkpt> +<trkpt lat="40.048447" lon="116.312721"><ele>53.11</ele> +<time>2024-11-11T11:51:39Z</time> +</trkpt> +<trkpt lat="40.048447" lon="116.312721"><ele>53.11</ele> +<time>2024-11-11T11:51:40Z</time> +</trkpt> +<trkpt lat="40.048443" lon="116.312714"><ele>53.19</ele> +<time>2024-11-11T11:51:41Z</time> +</trkpt> +<trkpt lat="40.048439" lon="116.312698"><ele>53.24</ele> +<time>2024-11-11T11:51:42Z</time> +</trkpt> +<trkpt lat="40.048424" lon="116.312668"><ele>53.24</ele> +<time>2024-11-11T11:51:43Z</time> +</trkpt> +<trkpt lat="40.048405" lon="116.312645"><ele>53.15</ele> +<time>2024-11-11T11:51:44Z</time> +</trkpt> +<trkpt lat="40.048389" lon="116.312607"><ele>53.15</ele> +<time>2024-11-11T11:51:45Z</time> +</trkpt> +<trkpt lat="40.048374" lon="116.312569"><ele>53.15</ele> +<time>2024-11-11T11:51:46Z</time> +</trkpt> +<trkpt lat="40.048355" lon="116.312531"><ele>52.68</ele> +<time>2024-11-11T11:51:47Z</time> +</trkpt> +<trkpt lat="40.048340" lon="116.312485"><ele>52.68</ele> +<time>2024-11-11T11:51:48Z</time> +</trkpt> +<trkpt lat="40.048325" lon="116.312439"><ele>52.63</ele> +<time>2024-11-11T11:51:49Z</time> +</trkpt> +<trkpt lat="40.048306" lon="116.312386"><ele>52.40</ele> +<time>2024-11-11T11:51:50Z</time> +</trkpt> +<trkpt lat="40.048283" lon="116.312332"><ele>52.37</ele> +<time>2024-11-11T11:51:51Z</time> +</trkpt> +<trkpt lat="40.048260" lon="116.312279"><ele>52.37</ele> +<time>2024-11-11T11:51:52Z</time> +</trkpt> +<trkpt lat="40.048237" lon="116.312225"><ele>52.37</ele> +<time>2024-11-11T11:51:53Z</time> +</trkpt> +<trkpt lat="40.048214" lon="116.312172"><ele>52.35</ele> +<time>2024-11-11T11:51:54Z</time> +</trkpt> +<trkpt lat="40.048191" lon="116.312111"><ele>52.33</ele> +<time>2024-11-11T11:51:55Z</time> +</trkpt> +<trkpt lat="40.048161" lon="116.312057"><ele>52.08</ele> +<time>2024-11-11T11:51:56Z</time> +</trkpt> +<trkpt lat="40.048134" lon="116.311996"><ele>52.08</ele> +<time>2024-11-11T11:51:57Z</time> +</trkpt> +<trkpt lat="40.048103" lon="116.311935"><ele>52.43</ele> +<time>2024-11-11T11:51:58Z</time> +</trkpt> +<trkpt lat="40.048077" lon="116.311874"><ele>52.43</ele> +<time>2024-11-11T11:51:59Z</time> +</trkpt> +<trkpt lat="40.048046" lon="116.311813"><ele>52.18</ele> +<time>2024-11-11T11:52:00Z</time> +</trkpt> +<trkpt lat="40.048019" lon="116.311752"><ele>52.01</ele> +<time>2024-11-11T11:52:01Z</time> +</trkpt> +<trkpt lat="40.047993" lon="116.311684"><ele>51.92</ele> +<time>2024-11-11T11:52:02Z</time> +</trkpt> +<trkpt lat="40.047962" lon="116.311623"><ele>51.92</ele> +<time>2024-11-11T11:52:03Z</time> +</trkpt> +<trkpt lat="40.047935" lon="116.311562"><ele>52.06</ele> +<time>2024-11-11T11:52:04Z</time> +</trkpt> +<trkpt lat="40.047905" lon="116.311501"><ele>52.19</ele> +<time>2024-11-11T11:52:05Z</time> +</trkpt> +<trkpt lat="40.047874" lon="116.311440"><ele>52.20</ele> +<time>2024-11-11T11:52:06Z</time> +</trkpt> +<trkpt lat="40.047844" lon="116.311378"><ele>52.30</ele> +<time>2024-11-11T11:52:07Z</time> +</trkpt> +<trkpt lat="40.047825" lon="116.311317"><ele>52.50</ele> +<time>2024-11-11T11:52:08Z</time> +</trkpt> +<trkpt lat="40.047798" lon="116.311256"><ele>52.53</ele> +<time>2024-11-11T11:52:09Z</time> +</trkpt> +<trkpt lat="40.047771" lon="116.311195"><ele>52.63</ele> +<time>2024-11-11T11:52:10Z</time> +</trkpt> +<trkpt lat="40.047741" lon="116.311134"><ele>52.63</ele> +<time>2024-11-11T11:52:11Z</time> +</trkpt> +<trkpt lat="40.047714" lon="116.311073"><ele>52.83</ele> +<time>2024-11-11T11:52:12Z</time> +</trkpt> +<trkpt lat="40.047688" lon="116.311012"><ele>52.91</ele> +<time>2024-11-11T11:52:13Z</time> +</trkpt> +<trkpt lat="40.047661" lon="116.310951"><ele>52.91</ele> +<time>2024-11-11T11:52:14Z</time> +</trkpt> +<trkpt lat="40.047634" lon="116.310890"><ele>52.63</ele> +<time>2024-11-11T11:52:15Z</time> +</trkpt> +<trkpt lat="40.047607" lon="116.310837"><ele>52.63</ele> +<time>2024-11-11T11:52:16Z</time> +</trkpt> +<trkpt lat="40.047585" lon="116.310783"><ele>52.63</ele> +<time>2024-11-11T11:52:17Z</time> +</trkpt> +<trkpt lat="40.047554" lon="116.310730"><ele>52.99</ele> +<time>2024-11-11T11:52:18Z</time> +</trkpt> +<trkpt lat="40.047531" lon="116.310669"><ele>53.00</ele> +<time>2024-11-11T11:52:19Z</time> +</trkpt> +<trkpt lat="40.047504" lon="116.310608"><ele>53.03</ele> +<time>2024-11-11T11:52:20Z</time> +</trkpt> +<trkpt lat="40.047478" lon="116.310555"><ele>53.03</ele> +<time>2024-11-11T11:52:21Z</time> +</trkpt> +<trkpt lat="40.047451" lon="116.310493"><ele>53.03</ele> +<time>2024-11-11T11:52:22Z</time> +</trkpt> +<trkpt lat="40.047428" lon="116.310432"><ele>52.86</ele> +<time>2024-11-11T11:52:23Z</time> +</trkpt> +<trkpt lat="40.047405" lon="116.310371"><ele>52.86</ele> +<time>2024-11-11T11:52:24Z</time> +</trkpt> +<trkpt lat="40.047379" lon="116.310310"><ele>52.62</ele> +<time>2024-11-11T11:52:25Z</time> +</trkpt> +<trkpt lat="40.047356" lon="116.310249"><ele>52.46</ele> +<time>2024-11-11T11:52:26Z</time> +</trkpt> +<trkpt lat="40.047329" lon="116.310188"><ele>52.46</ele> +<time>2024-11-11T11:52:27Z</time> +</trkpt> +<trkpt lat="40.047302" lon="116.310127"><ele>52.50</ele> +<time>2024-11-11T11:52:28Z</time> +</trkpt> +<trkpt lat="40.047279" lon="116.310074"><ele>52.50</ele> +<time>2024-11-11T11:52:29Z</time> +</trkpt> +<trkpt lat="40.047256" lon="116.310013"><ele>52.39</ele> +<time>2024-11-11T11:52:30Z</time> +</trkpt> +<trkpt lat="40.047234" lon="116.309952"><ele>52.39</ele> +<time>2024-11-11T11:52:31Z</time> +</trkpt> +<trkpt lat="40.047215" lon="116.309898"><ele>52.39</ele> +<time>2024-11-11T11:52:32Z</time> +</trkpt> +<trkpt lat="40.047195" lon="116.309837"><ele>52.39</ele> +<time>2024-11-11T11:52:33Z</time> +</trkpt> +<trkpt lat="40.047176" lon="116.309784"><ele>52.70</ele> +<time>2024-11-11T11:52:34Z</time> +</trkpt> +<trkpt lat="40.047153" lon="116.309738"><ele>52.70</ele> +<time>2024-11-11T11:52:35Z</time> +</trkpt> +<trkpt lat="40.047131" lon="116.309685"><ele>52.70</ele> +<time>2024-11-11T11:52:36Z</time> +</trkpt> +<trkpt lat="40.047115" lon="116.309631"><ele>52.52</ele> +<time>2024-11-11T11:52:37Z</time> +</trkpt> +<trkpt lat="40.047100" lon="116.309586"><ele>52.52</ele> +<time>2024-11-11T11:52:38Z</time> +</trkpt> +<trkpt lat="40.047092" lon="116.309547"><ele>52.68</ele> +<time>2024-11-11T11:52:39Z</time> +</trkpt> +<trkpt lat="40.047077" lon="116.309509"><ele>52.93</ele> +<time>2024-11-11T11:52:40Z</time> +</trkpt> +<trkpt lat="40.047066" lon="116.309471"><ele>52.96</ele> +<time>2024-11-11T11:52:41Z</time> +</trkpt> +<trkpt lat="40.047062" lon="116.309425"><ele>53.01</ele> +<time>2024-11-11T11:52:42Z</time> +</trkpt> +<trkpt lat="40.047070" lon="116.309387"><ele>52.96</ele> +<time>2024-11-11T11:52:43Z</time> +</trkpt> +<trkpt lat="40.047085" lon="116.309341"><ele>52.82</ele> +<time>2024-11-11T11:52:44Z</time> +</trkpt> +<trkpt lat="40.047112" lon="116.309319"><ele>52.60</ele> +<time>2024-11-11T11:52:45Z</time> +</trkpt> +<trkpt lat="40.047142" lon="116.309303"><ele>52.60</ele> +<time>2024-11-11T11:52:46Z</time> +</trkpt> +<trkpt lat="40.047173" lon="116.309280"><ele>52.64</ele> +<time>2024-11-11T11:52:47Z</time> +</trkpt> +<trkpt lat="40.047203" lon="116.309258"><ele>52.68</ele> +<time>2024-11-11T11:52:48Z</time> +</trkpt> +<trkpt lat="40.047237" lon="116.309235"><ele>52.75</ele> +<time>2024-11-11T11:52:49Z</time> +</trkpt> +<trkpt lat="40.047272" lon="116.309204"><ele>52.81</ele> +<time>2024-11-11T11:52:50Z</time> +</trkpt> +<trkpt lat="40.047310" lon="116.309189"><ele>52.78</ele> +<time>2024-11-11T11:52:51Z</time> +</trkpt> +<trkpt lat="40.047344" lon="116.309174"><ele>52.83</ele> +<time>2024-11-11T11:52:52Z</time> +</trkpt> +<trkpt lat="40.047382" lon="116.309151"><ele>52.83</ele> +<time>2024-11-11T11:52:53Z</time> +</trkpt> +<trkpt lat="40.047417" lon="116.309135"><ele>52.89</ele> +<time>2024-11-11T11:52:54Z</time> +</trkpt> +<trkpt lat="40.047459" lon="116.309120"><ele>52.89</ele> +<time>2024-11-11T11:52:55Z</time> +</trkpt> +<trkpt lat="40.047497" lon="116.309105"><ele>52.77</ele> +<time>2024-11-11T11:52:56Z</time> +</trkpt> +<trkpt lat="40.047531" lon="116.309097"><ele>52.45</ele> +<time>2024-11-11T11:52:57Z</time> +</trkpt> +<trkpt lat="40.047565" lon="116.309074"><ele>52.45</ele> +<time>2024-11-11T11:52:58Z</time> +</trkpt> +<trkpt lat="40.047596" lon="116.309059"><ele>52.33</ele> +<time>2024-11-11T11:52:59Z</time> +</trkpt> +<trkpt lat="40.047623" lon="116.309036"><ele>52.46</ele> +<time>2024-11-11T11:53:00Z</time> +</trkpt> +<trkpt lat="40.047653" lon="116.309021"><ele>52.39</ele> +<time>2024-11-11T11:53:01Z</time> +</trkpt> +<trkpt lat="40.047691" lon="116.309006"><ele>52.29</ele> +<time>2024-11-11T11:53:02Z</time> +</trkpt> +<trkpt lat="40.047726" lon="116.308998"><ele>52.12</ele> +<time>2024-11-11T11:53:03Z</time> +</trkpt> +<trkpt lat="40.047760" lon="116.308990"><ele>52.12</ele> +<time>2024-11-11T11:53:04Z</time> +</trkpt> +<trkpt lat="40.047794" lon="116.308975"><ele>52.10</ele> +<time>2024-11-11T11:53:05Z</time> +</trkpt> +<trkpt lat="40.047825" lon="116.308952"><ele>52.20</ele> +<time>2024-11-11T11:53:06Z</time> +</trkpt> +<trkpt lat="40.047855" lon="116.308945"><ele>52.51</ele> +<time>2024-11-11T11:53:07Z</time> +</trkpt> +<trkpt lat="40.047878" lon="116.308937"><ele>52.51</ele> +<time>2024-11-11T11:53:08Z</time> +</trkpt> +<trkpt lat="40.047901" lon="116.308922"><ele>52.51</ele> +<time>2024-11-11T11:53:09Z</time> +</trkpt> +<trkpt lat="40.047920" lon="116.308907"><ele>52.88</ele> +<time>2024-11-11T11:53:10Z</time> +</trkpt> +<trkpt lat="40.047932" lon="116.308891"><ele>52.96</ele> +<time>2024-11-11T11:53:11Z</time> +</trkpt> +<trkpt lat="40.047943" lon="116.308884"><ele>52.96</ele> +<time>2024-11-11T11:53:12Z</time> +</trkpt> +<trkpt lat="40.047951" lon="116.308884"><ele>52.96</ele> +<time>2024-11-11T11:53:13Z</time> +</trkpt> +<trkpt lat="40.047951" lon="116.308891"><ele>53.11</ele> +<time>2024-11-11T11:53:14Z</time> +</trkpt> +<trkpt lat="40.047951" lon="116.308891"><ele>53.11</ele> +<time>2024-11-11T11:53:15Z</time> +</trkpt> +</trkseg> +</trk> +</gpx> diff --git a/x_track/resource/upload_res.sh b/x_track/resource/upload_res.sh new file mode 100755 index 0000000000000000000000000000000000000000..38ad9eeeed9bed657718b7ce0af4a0870c651260 --- /dev/null +++ b/x_track/resource/upload_res.sh @@ -0,0 +1,3 @@ +adb push font /data +adb push images /data +adb push track /data diff --git a/x_track/src/App/App.cpp b/x_track/src/App/App.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d8bb17e7507e347981f922c3f63a06c1b864d053 --- /dev/null +++ b/x_track/src/App/App.cpp @@ -0,0 +1,105 @@ +/* + * MIT License + * Copyright (c) 2021 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "App.h" +#include "Config/Config.h" +#include "Frameworks/PageManager/PageManager.h" +#include "Service/DataProc/DataProc.h" +#include "UI/AppFactory.h" +#include "UI/Resource/ResourcePool.h" +#include "UI/StatusBar/StatusBar.h" + +struct AppContext { + DataBroker* broker; + DataProc::Global_Helper* global; + PageManager* manager; + Page::StatusBar* statusBar; +}; + +static void App_ParseArgsToEnv(AppContext_t* context, int argc, const char* argv[]) +{ + if (argc < 2) { + return; + } + + DataProc::Env_Helper env(context->broker->mainNode()); + + int i = 1; + while (i < argc) { + const char* key = argv[i++]; + const char* value = i < argc ? argv[i++] : nullptr; + LV_LOG_USER("Setting env: %s = %s", key, value); + env.set(key, value); + } + + context->global->publish(DataProc::GLOBAL_EVENT::APP_ARGS_PARSED); +} + +AppContext_t* App_CreateContext(int argc, const char* argv[]) +{ + AppContext_t* context = new AppContext; + + /* Resource pool manager */ + ResourcePool::init(); + + /* Data processor */ + context->broker = new DataBroker("Broker"); + DataProc_Init(context->broker); + + context->global = new DataProc::Global_Helper(context->broker->mainNode()); + context->global->publish(DataProc::GLOBAL_EVENT::DATA_PROC_INIT_FINISHED); + + App_ParseArgsToEnv(context, argc, argv); + + /* Page manager */ + context->manager = new PageManager(AppFactory::getInstance()); + AppFactory::getInstance()->intsallAll(context->manager); + context->global->publish(DataProc::GLOBAL_EVENT::PAGE_MANAGER_INIT_FINISHED, context->manager); + + /* StatusBar */ + context->statusBar = new Page::StatusBar(context->manager->getLayerTop()); + context->global->publish(DataProc::GLOBAL_EVENT::STATUS_BAR_INIT_FINISHED); + + /* App started */ + context->global->publish(DataProc::GLOBAL_EVENT::APP_STARTED); + + return context; +} + +uint32_t App_RunLoopExecute(AppContext_t* context) +{ + LV_ASSERT_NULL(context); + context->global->publish(DataProc::GLOBAL_EVENT::APP_RUN_LOOP_EXECUTE); + return context->broker->handleTimer(); +} + +void App_DestroyContext(AppContext_t* context) +{ + LV_ASSERT_NULL(context); + context->global->publish(DataProc::GLOBAL_EVENT::APP_STOPPED); + + delete context->manager; + delete context->statusBar; + delete context->broker; + ResourcePool::deinit(); + delete context; +} diff --git a/x_track/src/App/App.h b/x_track/src/App/App.h new file mode 100644 index 0000000000000000000000000000000000000000..3775dd4aedeb33b8268b1ec1bacf6f5e899ba83e --- /dev/null +++ b/x_track/src/App/App.h @@ -0,0 +1,57 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __APP_H +#define __APP_H + +#include <stdint.h> + +typedef struct AppContext AppContext_t; + +extern "C" { + +/** + * @brief Create app context + * + * @param argc Number of arguments + * @param argv Arguments + * @return AppContext_t* + */ +AppContext_t* App_CreateContext(int argc, const char* argv[]); + +/** + * @brief Run app loop + * + * @param context + */ +uint32_t App_RunLoopExecute(AppContext_t* context); + +/** + * @brief Destroy app context + * + * @param context + */ +void App_DestroyContext(AppContext_t* context); + +} /* extern "C" */ + +#endif diff --git a/x_track/src/App/Config/Config.h b/x_track/src/App/Config/Config.h new file mode 100644 index 0000000000000000000000000000000000000000..93217fc5739b202a99cca3b77865dcc964dcc114 --- /dev/null +++ b/x_track/src/App/Config/Config.h @@ -0,0 +1,93 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __CONFIG_H +#define __CONFIG_H + +/* clang-format off */ + +/*========================= + Application configuration + *=========================*/ + +/* Default time zone (GMT+ X)*/ +#define CONFIG_TIME_ZONE_DEFAULT 8 + +/* Default language code + * zh-CN: Chinese (PRC) + * en-US: English (United States) + * + * Language coding standards: https://www.andiamo.co.uk/resources/iso-language-codes + */ +#define CONFIG_LANGUAGE_DEFAULT "zh-CN" + +/* Default weight (kilogram) */ +#define CONFIG_WEIGHT_DEFAULT 65 + +/* Default GNSS data refresh period (milliseconds) */ +#ifndef CONFIG_GNSS_UPDATE_PERIOD +# define CONFIG_GNSS_UPDATE_PERIOD 1000 +#endif + +/* Resource path configuration */ +#ifndef CONFIG_RESOURCE_FS_LETTER +#define CONFIG_RESOURCE_FS_LETTER "/" +#endif + +#ifndef CONFIG_RESOURCE_DIR_PATH +#define CONFIG_RESOURCE_DIR_PATH "Resource" +#endif + +#define RESOURCE_MAKE_PATH_NO_LETTER(path) CONFIG_RESOURCE_DIR_PATH path +#define RESOURCE_MAKE_PATH(path) CONFIG_RESOURCE_FS_LETTER RESOURCE_MAKE_PATH_NO_LETTER(path) + +/* Image and font storage directory */ +#ifndef CONFIG_IMAGE_DIR_PATH +#define CONFIG_IMAGE_DIR_PATH RESOURCE_MAKE_PATH("/images") +#endif + +#define CONFIG_IMAGE_EXT_NAME ".png" + +#ifndef CONFIG_FONT_DIR_PATH +/* Freetype uses relative paths and does not add letter */ +#define CONFIG_FONT_DIR_PATH RESOURCE_MAKE_PATH_NO_LETTER("/font") +#endif + +#define CONFIG_FONT_EXT_NAME ".ttf" +#define FONT_MAKE_PATH(name) CONFIG_FONT_DIR_PATH "/" name CONFIG_FONT_EXT_NAME + +/* Default latitude and longitude */ +#define CONFIG_GNSS_LONGITUDE_DEFAULT 116.391332f +#define CONFIG_GNSS_LATITUDE_DEFAULT 39.907415f + +/* Track file storage directory */ +#define CONFIG_TRACK_RECORD_FILE_DIR_NAME RESOURCE_MAKE_PATH("/Track") + +/* Default map file storage directory */ +#define CONFIG_MAP_DIR_PATH_DEFAULT RESOURCE_MAKE_PATH("/MAP") + +/* Default map file extension name */ +#define CONFIG_MAP_EXT_NAME_DEFAULT CONFIG_IMAGE_EXT_NAME + +/* clang-format on */ + +#endif /* __CONFIG_H */ diff --git a/x_track/src/App/Service/DataProc/DP_Backlight.cpp b/x_track/src/App/Service/DataProc/DP_Backlight.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0456e62a1062e93e597cac0a1c5ee9602a8bab59 --- /dev/null +++ b/x_track/src/App/Service/DataProc/DP_Backlight.cpp @@ -0,0 +1,228 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "DataProc.h" +#include "Service/HAL/HAL.h" +#include "Utils/CommonMacro/CommonMacro.h" +#include "Utils/easing/easing.h" +#include <cmath> + +#define TIMER_PERIOD 30 + +// LN(1000) = 6.9077552789821f +#define PSYCHO2PHYSICAL 6.9077552789821f + +using namespace DataProc; + +class DP_Backlight { +public: + DP_Backlight(DataNode* node); + +private: + DataNode* _node; + const DataNode* _nodePower; + const DataNode* _nodeStorage; + const DataNode* _nodeSunRise; + DeviceObject* _dev; + easing_t _easing; + int _targetBrightness; + int _currentBrightness; + int _dayBrightness; + int _nightBrightness; + +private: + int onEvent(DataNode::EventParam_t* param); + void onTimer(); + void setBacklight(int brightness, bool anim, bool disable = false); + void setBacklight(int brightness); + void updateAutoBrightness(const Backlight_Info_t* info); +}; + +DP_Backlight::DP_Backlight(DataNode* node) + : _node(node) + , _nodePower(nullptr) + , _dev(nullptr) + , _targetBrightness(500) + , _currentBrightness(0) + , _dayBrightness(500) + , _nightBrightness(500) +{ + _dev = HAL::Manager()->getDevice("Backlight"); + if (!_dev) { + return; + } + + easing_init( + &_easing, + EASING_MODE_DEFAULT, + _easing_calc_Linear, + 0, + TIMER_PERIOD, + 0); + easing_set_tick_callback(HAL::GetTick); + + _nodePower = node->subscribe("Power"); + _nodeStorage = node->subscribe("Storage"); + _nodeSunRise = node->subscribe("SunRise"); + + Storage_Helper storage(node); + storage.structStart("backlight"); + storage.add("current", &_targetBrightness); + storage.add("day", &_dayBrightness); + storage.add("night", &_nightBrightness); + storage.structEnd(); + + node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_Backlight*)n->getUserData(); + return ctx->onEvent(param); + }); +} + +int DP_Backlight::onEvent(DataNode::EventParam_t* param) +{ + auto info = (Backlight_Info_t*)param->data_p; + + switch (param->event) { + case DataNode::EVENT_TIMER: + onTimer(); + break; + case DataNode::EVENT_NOTIFY: + if (!info->disable && (info->brightnessDay >= 0 || info->brightnessNight >= 0)) { + updateAutoBrightness(info); + } else { + setBacklight(info->brightness, info->anim, info->disable); + } + break; + case DataNode::EVENT_PULL: + info->brightness = _targetBrightness; + info->anim = false; + info->brightnessDay = _dayBrightness; + info->brightnessNight = _nightBrightness; + break; + case DataNode::EVENT_PUBLISH: + if (param->tran == _nodePower) { + auto powerInfo = (const Power_Info_t*)param->data_p; + if (powerInfo->isReadyToShutdown) { + /* shutdown */ + setBacklight(0, true); + } + } else if (param->tran == _nodeStorage) { + auto storageInfo = (const Storage_Info_t*)param->data_p; + if (storageInfo->cmd == STORAGE_CMD::LOAD) { + /* load success */ + setBacklight(_targetBrightness, true); + } + } else if (param->tran == _nodeSunRise) { + auto sunRiseInfo = (const SunRise_Info_t*)param->data_p; + if (sunRiseInfo->state == SUNRISE_STATE::DAY) { + setBacklight(_dayBrightness, true); + } else if (sunRiseInfo->state == SUNRISE_STATE::NIGHT) { + setBacklight(_nightBrightness, true); + } + } + break; + + default: + break; + } + + return DataNode::RES_OK; +} + +void DP_Backlight::onTimer() +{ + easing_update(&_easing); + int pos = easing_curpos(&_easing); + + setBacklight(pos); + + /* when animation is finish, stop timer */ + if (pos == _targetBrightness) { + _node->stopTimer(); + } +} + +void DP_Backlight::setBacklight(int brightness) +{ + /* record current brightness */ + _currentBrightness = brightness; + + /* gamma correction */ + if (brightness != 0 && brightness != 1000) { + brightness = (int)expf((float)brightness / 1000.0f * PSYCHO2PHYSICAL); + } + + _dev->write(&brightness, sizeof(brightness)); +} + +void DP_Backlight::setBacklight(int brightness, bool anim, bool disable) +{ + if (disable) { + /* stop animation */ + easing_stop(&_easing, _targetBrightness); + _node->stopTimer(); + setBacklight(0); + return; + } + + CM_VALUE_LIMIT(brightness, 0, 1000); + _targetBrightness = brightness; + + /* start animation */ + if (anim) { + easing_start_absolute(&_easing, _currentBrightness, _targetBrightness); + _node->startTimer(TIMER_PERIOD); + return; + } + + /* stop animation */ + easing_stop(&_easing, _targetBrightness); + _node->stopTimer(); + + /* set backlight directly */ + setBacklight(_targetBrightness); +} + +void DP_Backlight::updateAutoBrightness(const Backlight_Info_t* info) +{ + if (info->brightnessDay >= 0) { + _dayBrightness = info->brightnessDay; + } + + if (info->brightnessNight >= 0) { + _nightBrightness = info->brightnessNight; + } + + SunRise_Info_t sunRiseInfo; + if (_node->pull(_nodeSunRise, &sunRiseInfo, sizeof(sunRiseInfo)) != DataNode::RES_OK) { + return; + } + + if (sunRiseInfo.state == SUNRISE_STATE::DAY) { + setBacklight(_dayBrightness, info->anim); + } else if (sunRiseInfo.state == SUNRISE_STATE::NIGHT) { + setBacklight(_nightBrightness, info->anim); + } +} + +DATA_PROC_DESCRIPTOR_DEF(Backlight) diff --git a/x_track/src/App/Service/DataProc/DP_Clock.cpp b/x_track/src/App/Service/DataProc/DP_Clock.cpp new file mode 100755 index 0000000000000000000000000000000000000000..b9e22d44ac842c55d8468a5fb0953095e5380d3b --- /dev/null +++ b/x_track/src/App/Service/DataProc/DP_Clock.cpp @@ -0,0 +1,160 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Config/Config.h" +#include "DataProc.h" +#include "Service/HAL/HAL.h" +#include "Utils/Time/Time.h" +#include <stdlib.h> + +#define TIMEZONE_STR "timeZone" + +using namespace DataProc; + +class DP_Clock { +public: + DP_Clock(DataNode* node); + +private: + DataNode* _node; + const DataNode* _nodeGNSS; + const DataNode* _nodeStroage; + Env_Helper _env; + DeviceObject* _dev; + int _timeZone; + +private: + int onEvent(DataNode::EventParam_t* param); + bool calibrate(const HAL::Clock_Info_t* info); +}; + +DP_Clock::DP_Clock(DataNode* node) + : _node(node) + , _env(node) + , _dev(nullptr) + , _timeZone(CONFIG_TIME_ZONE_DEFAULT) +{ + _dev = HAL::Manager()->getDevice("Clock"); + if (!_dev) { + return; + } + + _nodeGNSS = node->subscribe("GNSS"); + _nodeStroage = node->subscribe("Storage"); + + Storage_Helper storage(node); + storage.add(TIMEZONE_STR, &_timeZone); + + _node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_Clock*)n->getUserData(); + return ctx->onEvent(param); + }, + DataNode::EVENT_PUBLISH | DataNode::EVENT_PULL | DataNode::EVENT_TIMER); + + node->startTimer(1000); +} + +int DP_Clock::onEvent(DataNode::EventParam_t* param) +{ + switch (param->event) { + case DataNode::EVENT_PUBLISH: { + if (param->tran == _nodeGNSS) { + auto gnssInfo = (HAL::GNSS_Info_t*)param->data_p; + + if (!gnssInfo->isVaild) { + return DataNode::RES_NO_DATA; + } + + if (calibrate(&gnssInfo->clock)) { + _node->unsubscribe("GNSS"); + _nodeGNSS = nullptr; + } + } else if (param->tran == _nodeStroage) { + auto info = (const Storage_Info_t*)param->data_p; + if (info->cmd == STORAGE_CMD::LOAD) { + _env.setInt(TIMEZONE_STR, _timeZone); + } + } else if (param->tran == _env) { + auto info = (const Env_Info_t*)param->data_p; + if (strcmp(info->key, TIMEZONE_STR) == 0) { + _timeZone = atoi(info->value); + + /* re-calibrate */ + _nodeGNSS = _node->subscribe("GNSS"); + } + } + + return DataNode::RES_OK; + } + + case DataNode::EVENT_PULL: { + /* Clock info pull request */ + int ret = _dev->read(param->data_p, param->size); + return ret == sizeof(HAL::Clock_Info_t) ? DataNode::RES_OK : DataNode::RES_NO_DATA; + } + + case DataNode::EVENT_TIMER: { + HAL::Clock_Info_t clock; + int ret = _dev->read(&clock, sizeof(clock)); + if (ret != sizeof(clock)) { + return DataNode::RES_NO_DATA; + } + return _node->publish(&clock, sizeof(clock)); + } + + default: + break; + } + + return DataNode::RES_UNKNOWN; +} + +bool DP_Clock::calibrate(const HAL::Clock_Info_t* info) +{ + setTime( + info->hour, + info->minute, + info->second, + info->day, + info->month, + info->year); + adjustTime(_timeZone * SECS_PER_HOUR); + + HAL::Clock_Info_t clock; + clock.year = (uint16_t)year(); + clock.month = (uint8_t)month(); + clock.day = (uint8_t)day(); + clock.week = 0; + clock.hour = (uint8_t)hour(); + clock.minute = (uint8_t)minute(); + clock.second = (uint8_t)second(); + clock.millisecond = 0; + + if (_dev->ioctl(CLOCK_IOCMD_CALIBRATE, &clock, sizeof(clock)) != DeviceObject::RES_OK) { + return false; + } + + return true; +} + +DATA_PROC_DESCRIPTOR_DEF(Clock) diff --git a/x_track/src/App/Service/DataProc/DP_Env.cpp b/x_track/src/App/Service/DataProc/DP_Env.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7759e209bc2581d97a06df10368cb414b388c685 --- /dev/null +++ b/x_track/src/App/Service/DataProc/DP_Env.cpp @@ -0,0 +1,115 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "DataProc.h" +#include "Utils/WString/WString.h" +#include <vector> + +using namespace DataProc; + +class DP_Env { +public: + DP_Env(DataNode* node); + +private: + typedef struct { + String key; + String value; + } key_pair_t; + +private: + DataNode* _node; + std::vector<key_pair_t> _keyPairs; + +private: + int onEvent(DataNode::EventParam_t* param); + int onSet(const Env_Info_t* info); + int onGet(Env_Info_t* info); +}; + +DP_Env::DP_Env(DataNode* node) + : _node(node) +{ + node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_Env*)n->getUserData(); + return ctx->onEvent(param); + }, + DataNode::EVENT_NOTIFY | DataNode::EVENT_PULL); +} + +int DP_Env::onEvent(DataNode::EventParam_t* param) +{ + if (param->size != sizeof(Env_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + + auto info = (Env_Info_t*)param->data_p; + + switch (param->event) { + case DataNode::EVENT_NOTIFY: { + auto res = onSet(info); + if (res == DataNode::RES_OK) { + _node->publish(info, sizeof(Env_Info_t)); + } + return res; + } + + case DataNode::EVENT_PULL: + return onGet(info); + + default: + break; + } + + return DataNode::RES_UNSUPPORTED_REQUEST; +} + +int DP_Env::onSet(const Env_Info_t* info) +{ + for (auto iter = _keyPairs.begin(); iter != _keyPairs.end(); ++iter) { + if (iter->key == info->key) { + iter->value = info->value; + return DataNode::RES_OK; + } + } + + key_pair_t pair = { info->key, info->value }; + _keyPairs.push_back(pair); + + return DataNode::RES_OK; +} + +int DP_Env::onGet(Env_Info_t* info) +{ + for (auto iter = _keyPairs.begin(); iter != _keyPairs.end(); ++iter) { + if (iter->key == info->key) { + info->value = iter->value.c_str(); + return DataNode::RES_OK; + } + } + + info->value = nullptr; + return DataNode::RES_NO_DATA; +} + +DATA_PROC_DESCRIPTOR_DEF(Env) diff --git a/x_track/src/App/Service/DataProc/DP_GNSS.cpp b/x_track/src/App/Service/DataProc/DP_GNSS.cpp new file mode 100644 index 0000000000000000000000000000000000000000..72c9fe492a15c0ac2639f0d6da841057d0fac589 --- /dev/null +++ b/x_track/src/App/Service/DataProc/DP_GNSS.cpp @@ -0,0 +1,317 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Config/Config.h" +#include "DataProc.h" +#include "Service/HAL/HAL.h" +#include "Service/i18n/lv_i18n.h" +#include "lvgl/lvgl.h" +#include <cmath> + +using namespace DataProc; + +#define GNSS_EPHEMERIS_DEFAULT_FILE_PATH RESOURCE_MAKE_PATH("/mgaoffline.ubx") +#define GNSS_EPHEMERIS_LOAD_PERIOD 10 +#define GNSS_EPHEMERIS_LOAD_BYTES 64 +#define GNSS_SIGNAL_QUALITY_GOOD 7 + +#ifndef GNSS_ERROR_THRESHOLD +#define GNSS_ERROR_THRESHOLD 2 +#endif + +class DP_GNSS { +public: + DP_GNSS(DataNode* node); + +private: + enum class STATUS { + DISCONNECT, + CONNECTED, + }; + +private: + DataNode* _node; + const DataNode* _nodeGlobal; + STATUS _nowStatus; + STATUS _lastStatus; + DeviceObject* _dev; + FeedbackGen_Helper _feedback; + MsgBox_Helper _msgbox; + Toast_Helper _toast; + LED_Helper _led; + lv_timer_t* _timer; + lv_fs_file_t _file; + +#if GNSS_ERROR_THRESHOLD > 0 + int16_t _lastLongitude; + int16_t _lastLatitude; + int16_t _errorCount; +#endif + +private: + int onEvent(DataNode::EventParam_t* param); + void onPublish(); + int onNotify(const GNSS_Config_Info_t* info); + bool readGNSSInfo(HAL::GNSS_Info_t* info); + bool loadEphemeris(const char* path); + static void onTimer(lv_timer_t* timer); +}; + +DP_GNSS::DP_GNSS(DataNode* node) + : _node(node) + , _nowStatus(STATUS::DISCONNECT) + , _lastStatus(STATUS::DISCONNECT) + , _dev(nullptr) + , _feedback(node) + , _msgbox(node) + , _toast(node) + , _led(node, LED_ID::BLUE) + , _timer(nullptr) +#if GNSS_ERROR_THRESHOLD > 0 + , _lastLongitude(0) + , _lastLatitude(0) + , _errorCount(0) +#endif +{ + _dev = HAL::Manager()->getDevice("GNSS"); + if (!_dev) { + return; + } + + _nodeGlobal = node->subscribe("Global"); + + node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_GNSS*)n->getUserData(); + return ctx->onEvent(param); + }, + DataNode::EVENT_PULL | DataNode::EVENT_NOTIFY | DataNode::EVENT_PUBLISH); + + loadEphemeris(GNSS_EPHEMERIS_DEFAULT_FILE_PATH); +} + +int DP_GNSS::onEvent(DataNode::EventParam_t* param) +{ + switch (param->event) { + case DataNode::EVENT_PULL: { + if (param->size != sizeof(HAL::GNSS_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + + if (!readGNSSInfo((HAL::GNSS_Info_t*)param->data_p)) { + return DataNode::RES_NO_DATA; + } + } break; + + case DataNode::EVENT_NOTIFY: { + if (param->size != sizeof(GNSS_Config_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + + return onNotify((const GNSS_Config_Info_t*)param->data_p); + } + + case DataNode::EVENT_PUBLISH: { + if (param->tran == _nodeGlobal) { + auto globalInfo = (const Global_Info_t*)param->data_p; + if (globalInfo->event == GLOBAL_EVENT::APP_RUN_LOOP_EXECUTE) { + onPublish(); + } + } + } break; + + default: + return DataNode::RES_UNSUPPORTED_REQUEST; + } + + return DataNode::RES_OK; +} + +void DP_GNSS::onPublish() +{ + if (_dev->ioctl(GNSS_IOCMD_UPDATE) != DeviceObject::RES_OK) { + return; + } + + HAL::GNSS_Info_t gnssInfo; + if (!readGNSSInfo(&gnssInfo)) { + static const LED_Squence_t seqError[] = { + { LED_STATUS::ON, 50 }, + { LED_STATUS::OFF, 50 }, + { LED_STATUS::ON, 50 }, + { LED_STATUS::OFF, 50 }, + { LED_STATUS::ON, 50 }, + { LED_STATUS::OFF, 50 }, + { LED_STATUS::STOP, 0 }, + }; + _led.start(seqError); + return; + } + + int satellites = gnssInfo.satellites; + + if (satellites > GNSS_SIGNAL_QUALITY_GOOD) { + _nowStatus = STATUS::CONNECTED; + } else if (satellites == 0) { + _nowStatus = STATUS::DISCONNECT; + } + + /* on status changed */ + if (_nowStatus != _lastStatus) { + _lastStatus = _nowStatus; + static const FEEDBACK_GEN_EFFECT effect[] = { + FEEDBACK_GEN_EFFECT::NOTIFICATION_ERROR, + FEEDBACK_GEN_EFFECT::NOTIFICATION_SUCCESS + }; + + _feedback.trigger(effect[(int)_nowStatus]); + + /* publish disconnect info */ + if (_nowStatus == STATUS::DISCONNECT) { + _node->publish(&gnssInfo, sizeof(gnssInfo)); + } + } + + static const LED_Squence_t seqSlow[] = { + { LED_STATUS::ON, 300 }, + { LED_STATUS::OFF, 0 }, + { LED_STATUS::STOP, 0 }, + }; + const LED_Squence_t* seq = seqSlow; + + /* publish info */ + if (gnssInfo.isVaild && satellites >= 3) { + _node->publish(&gnssInfo, sizeof(gnssInfo)); + + static const LED_Squence_t seqFast[] = { + { LED_STATUS::ON, 50 }, + { LED_STATUS::OFF, 0 }, + { LED_STATUS::STOP, 0 }, + }; + seq = seqFast; + } + + _led.start(seq); +} + +int DP_GNSS::onNotify(const GNSS_Config_Info_t* info) +{ + switch (info->cmd) { + case GNSS_CMD::LOAD_EPHEMERIS: { + auto path = GNSS_EPHEMERIS_DEFAULT_FILE_PATH; + + /* use custom path */ + if (info->param.ephemerisPath) { + path = info->param.ephemerisPath; + } + + return loadEphemeris(path) ? DataNode::RES_OK : DataNode::RES_NO_DATA; + } + + default: + break; + } + + return DataNode::RES_UNSUPPORTED_REQUEST; +} + +bool DP_GNSS::readGNSSInfo(HAL::GNSS_Info_t* info) +{ + if (_dev->read(info, sizeof(HAL::GNSS_Info_t)) != sizeof(HAL::GNSS_Info_t)) { + return false; + } + +#if GNSS_ERROR_THRESHOLD > 0 + if (_lastLatitude != 0 && _lastLongitude != 0 + && (std::fabs(info->latitude - _lastLatitude) > GNSS_ERROR_THRESHOLD + || std::fabs(info->longitude - _lastLongitude) > GNSS_ERROR_THRESHOLD)) { + + _errorCount++; + + char buf[64]; + lv_snprintf(buf, sizeof(buf), + "Latitude: %f\nLongitude: %f\nError Count: %d", + info->latitude, info->longitude, _errorCount); + static const char* btns[] = { "OK", "" }; + _msgbox.show("GNSS ERROR", buf, btns); + return false; + } + + _lastLatitude = info->latitude; + _lastLongitude = info->longitude; +#endif + + return true; +} + +bool DP_GNSS::loadEphemeris(const char* path) +{ + if (_timer) { + LV_LOG_WARN("GNSS ephemeris is loading, please wait..."); + return false; + } + + lv_fs_res_t res = lv_fs_open(&_file, path, LV_FS_MODE_RD); + if (res != LV_FS_RES_OK) { + LV_LOG_WARN("open %s failed: %d", path, res); + return false; + } + + _timer = lv_timer_create(onTimer, GNSS_EPHEMERIS_LOAD_PERIOD, this); + return true; +} + +void DP_GNSS::onTimer(lv_timer_t* timer) +{ + auto self = (DP_GNSS*)lv_timer_get_user_data(timer); + + uint32_t rd; + uint8_t buff[GNSS_EPHEMERIS_LOAD_BYTES]; + auto res = lv_fs_read(&self->_file, buff, sizeof(buff), &rd); + if (res != LV_FS_RES_OK) { + goto onFinished; + } + + if (rd < 1) { + LV_LOG_WARN("read EOF Finished"); + self->_toast.show("%s %s", _("EPHEMERIS"), _("LOAD_SUCCESS")); + goto onFinished; + } + + if (self->_dev->write(buff, rd) < 0) { + LV_LOG_ERROR("write GNSS failed"); + goto onFinished; + } + + self->_led.on(); + + return; + +onFinished: + lv_fs_close(&self->_file); + lv_timer_del(self->_timer); + self->_timer = nullptr; + + self->_led.off(); +} + +DATA_PROC_DESCRIPTOR_DEF(GNSS) diff --git a/x_track/src/App/Service/DataProc/DP_Global.cpp b/x_track/src/App/Service/DataProc/DP_Global.cpp new file mode 100644 index 0000000000000000000000000000000000000000..39efdf7aecc55bcdf2fd9b9602aee1ee4ffd2216 --- /dev/null +++ b/x_track/src/App/Service/DataProc/DP_Global.cpp @@ -0,0 +1,58 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "DataProc.h" + +using namespace DataProc; + +class DP_Global { +public: + DP_Global(DataNode* node); + +private: + DataNode* _node; + +private: + int onEvent(DataNode::EventParam_t* param); +}; + +DP_Global::DP_Global(DataNode* node) + : _node(node) +{ + _node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_Global*)n->getUserData(); + return ctx->onEvent(param); + }, + DataNode::EVENT_NOTIFY); +} + +int DP_Global::onEvent(DataNode::EventParam_t* param) +{ + if (param->size != sizeof(Global_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + + return _node->publish(param->data_p, param->size); +} + +DATA_PROC_DESCRIPTOR_DEF(Global) diff --git a/x_track/src/App/Service/DataProc/DP_MapInfo.cpp b/x_track/src/App/Service/DataProc/DP_MapInfo.cpp new file mode 100644 index 0000000000000000000000000000000000000000..16f4935337928ca9cefa3ef657a109dbc622a1b1 --- /dev/null +++ b/x_track/src/App/Service/DataProc/DP_MapInfo.cpp @@ -0,0 +1,187 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Config/Config.h" +#include "DataProc.h" +#include "lvgl/lvgl.h" +#include <stdlib.h> + +#define MAP_LEVEL_MIN_DEFAULT 3 +#define MAP_LEVEL_MAX_DEFAULT 16 +#define MAP_LEVEL_MIN 0 +#define MAP_LEVEL_MAX 19 + +using namespace DataProc; + +class DP_MapInfo { +public: + DP_MapInfo(DataNode* node); + +private: + Map_Info_t _info; + char _path[32]; + char _ext[8]; + bool _isSearched; + +private: + int onEvent(DataNode::EventParam_t* param); + bool searchDirLevel(const char* dirName, uint8_t* min, uint8_t* max); +}; + +DP_MapInfo::DP_MapInfo(DataNode* node) + : _path { CONFIG_MAP_DIR_PATH_DEFAULT } + , _ext { CONFIG_MAP_EXT_NAME_DEFAULT } + , _isSearched(false) +{ + _info.path = _path; + _info.ext = _ext; + _info.levelMin = MAP_LEVEL_MIN_DEFAULT; + _info.levelMax = MAP_LEVEL_MAX_DEFAULT; + _info.coordTrans = false; + + node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_MapInfo*)n->getUserData(); + return ctx->onEvent(param); + }, + DataNode::EVENT_PULL | DataNode::EVENT_NOTIFY); + + Storage_Helper storage(node); + storage.structStart("map"); + storage.add("path", _path, sizeof(_path), STORAGE_TYPE::STRING); + storage.add("ext", _ext, sizeof(_ext), STORAGE_TYPE::STRING); + storage.add("coordTrans", &_info.coordTrans); + storage.structEnd(); +} + +int DP_MapInfo::onEvent(DataNode::EventParam_t* param) +{ + if (param->size != sizeof(Map_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + + auto info = (Map_Info_t*)param->data_p; + + switch (param->event) { + case DataNode::EVENT_PULL: { + if (!_isSearched) { + searchDirLevel(_path, &_info.levelMin, &_info.levelMax); + + LV_LOG_USER( + "Map path: %s, ext: %s, coord: %s, level min = %d, max = %d", + _info.path, + _info.ext, + _info.coordTrans ? "GCJ02" : "WGS84", + _info.levelMin, + _info.levelMax); + + _isSearched = true; + } + + *info = _info; + } break; + + case DataNode::EVENT_NOTIFY: { + _info.coordTrans = info->coordTrans; + + if (info->path && info->path != _path) { + strncpy(_path, info->path, sizeof(_path)); + _path[sizeof(_path) - 1] = '\0'; + } + + if (info->ext && info->ext != _ext) { + strncpy(_ext, info->ext, sizeof(_ext)); + _ext[sizeof(_ext) - 1] = '\0'; + } + } break; + + default: + break; + } + + return DataNode::RES_OK; +} + +bool DP_MapInfo::searchDirLevel(const char* dirName, uint8_t* min, uint8_t* max) +{ + lv_fs_dir_t dir; + + if (lv_fs_dir_open(&dir, dirName) != LV_FS_RES_OK) { + LV_LOG_ERROR("%s open faild", dirName); + return false; + } + + LV_LOG_USER("%s open success", dirName); + + uint8_t levelMin = MAP_LEVEL_MAX; + uint8_t levelMax = MAP_LEVEL_MIN; + int levelCnt = MAP_LEVEL_MAX + 1; + bool retval = false; + + char name[128]; + while (levelCnt--) { + lv_fs_res_t res = lv_fs_dir_read(&dir, name, sizeof(name)); + + if (name[0] == '\0' || res != LV_FS_RES_OK) { + break; + } + LV_LOG_INFO("dir: %s", name); + + if (name[0] == '/') { + + /* skip "/" */ + if (name[1] == '\0') { + continue; + } + + retval = true; + int level = atoi(name + 1); + + if (level < MAP_LEVEL_MIN || level > MAP_LEVEL_MAX) { + LV_LOG_ERROR("Error level = %d", level); + retval = false; + break; + } + + if (level < levelMin) { + levelMin = level; + } + + if (level > levelMax) { + levelMax = level; + } + } + } + + if (retval) { + *min = levelMin; + *max = levelMax; + } else { + LV_LOG_WARN("search failed!"); + } + + lv_fs_dir_close(&dir); + + return retval; +} + +DATA_PROC_DESCRIPTOR_DEF(MapInfo) diff --git a/x_track/src/App/Service/DataProc/DP_PageNavi.cpp b/x_track/src/App/Service/DataProc/DP_PageNavi.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8a63398e10572ef39c1fdf25873e9d597c553e22 --- /dev/null +++ b/x_track/src/App/Service/DataProc/DP_PageNavi.cpp @@ -0,0 +1,228 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "DataProc.h" +#include "Frameworks/PageManager/PageManager.h" +#include "UI/AppFactory.h" + +#define IS_STR_EQ(STR1, STR2) (strcmp(STR1, STR2) == 0) + +using namespace DataProc; + +class DP_PageNavi { +public: + DP_PageNavi(DataNode* node); + +private: + DataNode* _node; + const DataNode* _nodeGlobal; + Env_Helper _env; + PageManager* _manager; + bool _enable; + lv_style_t _pageStyle; + +private: + int onEvent(DataNode::EventParam_t* param); + int onNotify(const PageNavi_Info_t* info); + int onGlobalEvent(const Global_Info_t* info); + int naviToPage(PAGE_NAVI_CMD cmd, const char* name = nullptr); + static PageManager::RES_TYPE onPageEvent(PageManager::EVENT* event); +}; + +DP_PageNavi::DP_PageNavi(DataNode* node) + : _node(node) + , _env(node) +{ + _nodeGlobal = node->subscribe("Global"); + + node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_PageNavi*)n->getUserData(); + return ctx->onEvent(param); + }, + DataNode::EVENT_PUBLISH | DataNode::EVENT_NOTIFY); + + APP_DESCRIPTOR_IMPORT(Dashboard); + APP_DESCRIPTOR_IMPORT(LiveMap); + APP_DESCRIPTOR_IMPORT(Shutdown); + APP_DESCRIPTOR_IMPORT(Startup); + APP_DESCRIPTOR_IMPORT(SystemInfos); +} + +int DP_PageNavi::onEvent(DataNode::EventParam_t* param) +{ + if (param->event == DataNode::EVENT_NOTIFY) { + if (param->size != sizeof(PageNavi_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + + return onNotify((const PageNavi_Info_t*)param->data_p); + } + + if (param->tran == _env) { + auto info = (const Env_Info_t*)param->data_p; + if (IS_STR_EQ(info->key, "pagenavi")) { + _enable = IS_STR_EQ(info->value, "enable"); + } + } + + if (param->tran == _nodeGlobal) { + return onGlobalEvent((const Global_Info_t*)param->data_p); + } + + if (!_enable) { + return DataNode::RES_UNSUPPORTED_REQUEST; + } + + return DataNode::RES_UNSUPPORTED_REQUEST; +} + +int DP_PageNavi::onNotify(const PageNavi_Info_t* info) +{ + auto retval = PageManager::RES_TYPE::ERR_UNKNOWN; + + switch (info->cmd) { + case PAGE_NAVI_CMD::PUSH: { + retval = _manager->push(info->name); + } break; + + case PAGE_NAVI_CMD::POP: { + retval = _manager->pop(); + } break; + + default: + break; + } + + return retval == PageManager::RES_TYPE::OK ? DataNode::RES_OK : DataNode::RES_UNSUPPORTED_REQUEST; +} + +int DP_PageNavi::onGlobalEvent(const Global_Info_t* info) +{ + switch (info->event) { + case GLOBAL_EVENT::PAGE_MANAGER_INIT_FINISHED: { + _manager = (PageManager*)info->param; + +#if defined(PAGE_HOR_RES) && PAGE_HOR_RES > 0 && defined(PAGE_VER_RES) && PAGE_VER_RES > 0 + lv_obj_t* scr = lv_obj_create(lv_scr_act()); + lv_obj_remove_style_all(scr); + lv_obj_set_size(scr, PAGE_HOR_RES, PAGE_VER_RES); + lv_obj_center(scr); + _manager->setRootParent(scr); + + lv_obj_t* layer_top = lv_obj_create(lv_layer_top()); + lv_obj_remove_style_all(layer_top); + lv_obj_remove_flag(layer_top, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_remove_flag(layer_top, LV_OBJ_FLAG_CLICKABLE); + lv_obj_set_size(layer_top, PAGE_HOR_RES, PAGE_VER_RES); + lv_obj_center(layer_top); + _manager->setLayerTop(layer_top); +#endif + + /* Screen style */ + lv_obj_set_style_pad_all(_manager->getRootParent(), 0, 0); + lv_obj_clear_flag(_manager->getRootParent(), LV_OBJ_FLAG_SCROLLABLE); + + /* Page style */ + lv_style_init(&_pageStyle); + lv_style_set_size(&_pageStyle, lv_pct(100), lv_pct(100)); + lv_style_set_pad_all(&_pageStyle, 0); + lv_style_set_radius(&_pageStyle, 0); + lv_style_set_border_width(&_pageStyle, 0); + lv_style_set_text_font(&_pageStyle, lv_theme_get_font_normal(nullptr)); + _manager->setRootDefaultStyle(&_pageStyle); + + /* Page event hooker */ + _manager->setEventCallback(onPageEvent, this); + } break; + + case GLOBAL_EVENT::STATUS_BAR_INIT_FINISHED: { + /* Update StatusBar padding */ + lv_style_set_pad_top(&_pageStyle, _env.getInt("statusbar-padding-top")); + } break; + + case GLOBAL_EVENT::APP_STARTED: { + /* Open page */ + _manager->push("Startup"); + } break; + + case GLOBAL_EVENT::APP_STOPPED: { + /* Close page */ + _manager->setRootDefaultStyle(nullptr); + lv_style_reset(&_pageStyle); + } break; + + default: + break; + } + + return DataNode::RES_OK; +} + +int DP_PageNavi::naviToPage(PAGE_NAVI_CMD cmd, const char* name) +{ + PageNavi_Info_t info; + info.cmd = cmd; + info.name = name; + return onNotify(&info); +} + +PageManager::RES_TYPE DP_PageNavi::onPageEvent(PageManager::EVENT* event) +{ + auto self = (DP_PageNavi*)event->userData; + + switch (event->type) { + case PageManager::EVENT_TYPE::PAGE_STATE_CHANGED: { + auto state = *(const PageBase::STATE*)event->param; + + /* Set default env when page did appear */ + if (state == PageBase::STATE::DID_APPEAR) { + self->_env.set("statusbar-opa", "cover"); + self->_env.set("navibar", "enable"); + self->_env.set("statusbar", "enable"); + self->_env.set("pagenavi", "enable"); + } + } break; + + case PageManager::EVENT_TYPE::PAGE_PUSH: + case PageManager::EVENT_TYPE::PAGE_POP: { + PageNavi_Info_t info; + info.cmd = event->type == PageManager::EVENT_TYPE::PAGE_PUSH ? PAGE_NAVI_CMD::PUSH : PAGE_NAVI_CMD::POP; + info.name = (const char*)event->param; + auto res = self->_node->publish(&info, sizeof(info)); + + /* if the page navigation is intercept, stop process */ + if (res == DataNode::RES_STOP_PROCESS) { + return PageManager::RES_TYPE::STOP_PROCESS; + } + + return PageManager::RES_TYPE::OK; + } break; + + default: + break; + } + + return PageManager::RES_TYPE::OK; +} + +DATA_PROC_DESCRIPTOR_DEF(PageNavi); diff --git a/x_track/src/App/Service/DataProc/DP_Power.cpp b/x_track/src/App/Service/DataProc/DP_Power.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3aea62711ca001589204f60c5ef4c2e0f044079c --- /dev/null +++ b/x_track/src/App/Service/DataProc/DP_Power.cpp @@ -0,0 +1,302 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "DataProc.h" +#include "Service/HAL/HAL.h" +#include "Service/HAL/HAL_Log.h" +#include "Service/i18n/lv_i18n.h" + +using namespace DataProc; + +/* clang-format off */ + +#define POWER_SYSTEM_VOLTAGE (2800) // mV +#define POWER_FORCE_SHUTDOWN_VOLTAGE (3100) // mV +#define POWER_FORCE_SHUTDOWN_TIMEOUT (10 * 1000) // ms + +/* clang-format on */ + +class DP_Power { +public: + DP_Power(DataNode* node); + +private: + enum class AUTO_SHUTDOWN_STATE { + IDLE, + ACTIVE, + WAIT_USER_CONFIRM, + USER_CANCELED, + USER_ACCEPTED, + }; + +private: + DataNode* _node; + const DataNode* _nodeBtn; + MsgBox_Helper _msgBox; + Env_Helper _env; + DeviceObject* _dev; + Power_Info_t _info; + uint32_t _lastTick; + uint32_t _pressStartTick; + + uint32_t _wakeUpTick; + int _lockCount; + + AUTO_SHUTDOWN_STATE _autoShutdownState; + +private: + int onEvent(DataNode::EventParam_t* param); + void onPowerNotify(const Power_Info_t* info); + void onMsgBoxNotify(const MsgBox_Info_t* info); + void requestShutdown(uint32_t delayTime = 2000); + void checkShutdown(); + void publishInfo(); + void notifyUserReadyToShutdown(const char* title, const char* text); +}; + +DP_Power::DP_Power(DataNode* node) + : _node(node) + , _msgBox(node) + , _env(node) + , _lockCount(0) + , _autoShutdownState(AUTO_SHUTDOWN_STATE::ACTIVE) +{ + _dev = HAL::Manager()->getDevice("Power"); + if (!_dev) { + return; + } + + /* power on */ + _dev->ioctl(POWER_IOCMD_POWER_ON); + int sysVoltage = POWER_SYSTEM_VOLTAGE; + _dev->ioctl(POWER_IOCMD_SET_SYS_VOLTAGE, &sysVoltage, sizeof(sysVoltage)); + + /* init info */ + Storage_Helper storage(node); + storage.addArray("batteryUseTime", &_info.batteryUseTime, sizeof(uint32_t), 2, STORAGE_TYPE::INT); + storage.add("autoShutdownTime", &_info.autoShutdownTime); + + _nodeBtn = node->subscribe("Button"); + + _node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_Power*)n->getUserData(); + return ctx->onEvent(param); + }); + _node->startTimer(1000); + + _lastTick = HAL::GetTick(); + _wakeUpTick = HAL::GetTick(); +} + +int DP_Power::onEvent(DataNode::EventParam_t* param) +{ + switch (param->event) { + case DataNode::EVENT_TIMER: + checkShutdown(); + publishInfo(); + break; + + case DataNode::EVENT_PULL: + if (param->size != sizeof(Power_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + memcpy(param->data_p, &_info, sizeof(Power_Info_t)); + break; + + case DataNode::EVENT_NOTIFY: + if (param->tran == _msgBox) { + onMsgBoxNotify((MsgBox_Info_t*)param->data_p); + } else { + if (param->size != sizeof(Power_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + onPowerNotify((Power_Info_t*)param->data_p); + } + break; + + default: + return DataNode::RES_UNSUPPORTED_REQUEST; + } + + return DataNode::RES_OK; +} + +void DP_Power::onPowerNotify(const Power_Info_t* info) +{ + switch (info->cmd) { + case POWER_CMD::SHUTDOWN: { + requestShutdown(info->delayShutdownTime); + } break; + + case POWER_CMD::REBOOT: { + _dev->ioctl(POWER_IOCMD_REBOOT); + } break; + + case POWER_CMD::LOCK_WAKEUP: { + _lockCount++; + // HAL_LOG_INFO("Lock wakeup, count = %d", _lockCount); + } break; + + case POWER_CMD::UNLOCK_WAKEUP: { + _lockCount--; + // HAL_LOG_INFO("Unlock wakeup, count = %d", _lockCount); + + if (_lockCount < 0) { + HAL_LOG_WARN("Error unlock wakeup"); + _lockCount = 0; + } + + /* update wake up tick */ + _wakeUpTick = HAL::GetTick(); + } break; + + case POWER_CMD::KICK_WAKEUP: { + /* update wake up tick */ + _wakeUpTick = HAL::GetTick(); + } break; + + case POWER_CMD::SET_AUTO_SHUTDOWN_TIME: { + _info.autoShutdownTime = info->autoShutdownTime; + } break; + + case POWER_CMD::RESET_FUEL_GAUGE: { + _dev->ioctl(POWER_IOCMD_GAUGE_RESET); + } break; + + default: + break; + } +} + +void DP_Power::onMsgBoxNotify(const MsgBox_Info_t* info) +{ + switch (info->activeBtn) { + case 0: + _autoShutdownState = AUTO_SHUTDOWN_STATE::USER_CANCELED; + HAL_LOG_WARN("shutdown canceled"); + break; + case 1: + _autoShutdownState = AUTO_SHUTDOWN_STATE::USER_ACCEPTED; + requestShutdown(); + break; + + default: + break; + } +} + +void DP_Power::requestShutdown(uint32_t delayTime) +{ + HAL_LOG_WARN("Shutdown requested"); + + _info.isReadyToShutdown = true; + + /* delay to shutdown */ + _node->setTimerPeriod(delayTime); + + /* notify storage to save data */ + _env.set("storage", "save"); + + /* publish shutdown info */ + _node->publish(&_info, sizeof(_info)); +} + +void DP_Power::checkShutdown() +{ + /* check shutdown request */ + if (_info.isReadyToShutdown) { + _dev->ioctl(POWER_IOCMD_POWER_OFF); + _node->stopTimer(); + return; + } + + switch (_autoShutdownState) { + case AUTO_SHUTDOWN_STATE::ACTIVE: { + if (_info.voltage > 0 && _info.voltage < POWER_FORCE_SHUTDOWN_VOLTAGE) { + HAL_LOG_WARN("Voltage = %dmV is too low", _info.voltage); + _info.isBatteryLow = true; + notifyUserReadyToShutdown(_("LOW_BATTERY"), _("SHUTDOWN?")); + } + + /* check auto shutdown */ + if (_info.autoShutdownTime > 0 && _lockCount == 0) { + if (HAL::GetTickElaps(_wakeUpTick) > _info.autoShutdownTime * 1000U) { + HAL_LOG_WARN("Auto shutdown after %dsec", _info.autoShutdownTime); + notifyUserReadyToShutdown(_("NO_OPERATION"), _("SHUTDOWN?")); + } + } + } break; + + default: + break; + } +} + +void DP_Power::publishInfo() +{ + /* if ready to shutdown, do not publish info */ + if (_info.isReadyToShutdown) { + return; + } + + /* read power info */ + HAL::Power_Info_t info; + if (_dev->read(&info, sizeof(info)) != sizeof(info)) { + return; + } + + /* basic info */ + _info.voltage = info.voltage; + _info.level = info.level; + _info.isReady = info.isReady; + _info.isCharging = info.isCharging; + + uint32_t elaps = HAL::GetTickElaps(_lastTick); + _lastTick = HAL::GetTick(); + + if (info.isCharging) { + _info.batteryUseTime = 0; + } else { + _info.batteryUseTime += elaps; + } + + _info.uptime += elaps; + + _node->publish(&_info, sizeof(_info)); +} + +void DP_Power::notifyUserReadyToShutdown(const char* title, const char* text) +{ + static const char* btns[] = { _("NO"), _("YES"), "" }; + + MsgBox_Info_t info; + info.title = title; + info.txt = text; + info.btns = btns; + info.autoSelectBtn = 1; + _msgBox.show(&info); + + _autoShutdownState = AUTO_SHUTDOWN_STATE::WAIT_USER_CONFIRM; +} + +DATA_PROC_DESCRIPTOR_DEF(Power) diff --git a/x_track/src/App/Service/DataProc/DP_Recorder.cpp b/x_track/src/App/Service/DataProc/DP_Recorder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..607a1690983bde26cabc709bb30217238befdbe9 --- /dev/null +++ b/x_track/src/App/Service/DataProc/DP_Recorder.cpp @@ -0,0 +1,396 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Config/Config.h" +#include "DataProc.h" +#include "Service/HAL/HAL.h" +#include "Service/i18n/lv_i18n.h" +#include "Utils/GPX/GPX.h" +#include "lvgl/lvgl.h" + +using namespace DataProc; + +#define IS_STR_EQ(STR1, STR2) (strcmp(STR1, STR2) == 0) + +#define RECORDER_GPX_TIME_FMT "%d-%02d-%02dT%02d:%02d:%02dZ" +#define RECORDER_GPX_MONTH_FMT "%d_%02d" +#define RECORDER_GPX_FILE_NAME CONFIG_TRACK_RECORD_FILE_DIR_NAME "/" RECORDER_GPX_MONTH_FMT "/TRK_%d%02d%02d_%02d%02d%02d.gpx" + +class DP_Recorder { +public: + DP_Recorder(DataNode* node); + +private: + DataNode* _node; + const DataNode* _nodeClock; + const DataNode* _nodeGNSS; + const DataNode* _nodePower; + const DataNode* _nodeSlope; + Env_Helper _env; + bool _autoRec; + bool _autoRecFinish; + Recorder_Info_t _recInfo; + lv_fs_file_t _file; + GPX _gpx; + +private: + int onEvent(DataNode::EventParam_t* param); + int onPublish(DataNode::EventParam_t* param); + int onNotify(Recorder_Info_t* info); + bool onAutoRecord(); + bool recStart(uint16_t time); + bool recStop(); + void recPoint(const HAL::GNSS_Info_t* gnssInfo, const Slope_Info_t* slopeInfo = nullptr); + void showToast(const char* msg); + void setEnv(const char* value); + int notifyFilter(bool active); + lv_fs_res_t writeFile(lv_fs_file_t* file_p, const char* str); + int getTimeString(const char* format, char* buf, uint32_t size); + int makeDir(const char* path); +}; + +DP_Recorder::DP_Recorder(DataNode* node) + : _node(node) + , _env(node) + , _autoRec(true) + , _autoRecFinish(false) + , _file { 0 } +{ + _nodeClock = node->subscribe("Clock"); + _nodeGNSS = node->subscribe("GNSS"); + _nodePower = node->subscribe("Power"); + _nodeSlope = node->subscribe("Slope"); + node->subscribe("TrackFilter"); + node->subscribe("Toast"); + node->subscribe("Version"); + + /* create Track dir */ + makeDir(CONFIG_TRACK_RECORD_FILE_DIR_NAME); + + Storage_Helper storage(node); + storage.add("autoRec", &_autoRec); + + node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_Recorder*)n->getUserData(); + return ctx->onEvent(param); + }); +} + +int DP_Recorder::onEvent(DataNode::EventParam_t* param) +{ + switch (param->event) { + case DataNode::EVENT_PUBLISH: + return onPublish(param); + + case DataNode::EVENT_PULL: + if (param->size != sizeof(Recorder_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + + _recInfo.autoRec = _autoRec; + memcpy(param->data_p, &(_recInfo), param->size); + break; + + case DataNode::EVENT_NOTIFY: + if (param->size != sizeof(Recorder_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + + if (!onNotify((Recorder_Info_t*)param->data_p)) { + return DataNode::RES_UNSUPPORTED_REQUEST; + } + break; + + default: + break; + } + + return DataNode::RES_OK; +} + +int DP_Recorder::onPublish(DataNode::EventParam_t* param) +{ + if (param->tran == _nodeGNSS) { + auto info = (HAL::GNSS_Info_t*)param->data_p; + + if (info->isVaild && _autoRec && !_autoRecFinish) { + onAutoRecord(); + } + + if (_recInfo.active) { + Slope_Info_t slopeInfo; + bool hasSlope = _node->pull(_nodeSlope, &slopeInfo, sizeof(slopeInfo)) == DataNode::RES_OK; + + recPoint(info, hasSlope ? &slopeInfo : nullptr); + } + + return DataNode::RES_OK; + } + + if (param->tran == _nodePower) { + auto info = (Power_Info_t*)param->data_p; + if (info->isReadyToShutdown) { + Recorder_Info_t recInfo; + recInfo.active = false; + return onNotify(&recInfo); + } + + return DataNode::RES_OK; + } + + if (param->tran == _env) { + auto info = (const Env_Info_t*)param->data_p; + if (IS_STR_EQ(info->key, "storage") && IS_STR_EQ(info->value, "save")) { + Recorder_Info_t recInfo; + recInfo.active = false; + return onNotify(&recInfo); + } + + return DataNode::RES_OK; + } + + return DataNode::RES_UNSUPPORTED_REQUEST; +} + +int DP_Recorder::onNotify(Recorder_Info_t* info) +{ + if (info->cmd == RECORDER_CMD::ENABLE_AUTO_REC) { + _autoRec = true; + } else if (info->cmd == RECORDER_CMD::DISABLE_AUTO_REC) { + _autoRec = false; + } + + if (info->active) { + if (!recStart(info->time)) { + return DataNode::RES_NO_DATA; + } + + setEnv("REC"); + notifyFilter(true); + showToast(_("START_RECORD")); + _node->publish(info, sizeof(Recorder_Info_t)); + return DataNode::RES_OK; + } + + if (!recStop()) { + return DataNode::RES_NO_DATA; + } + + setEnv(""); + notifyFilter(false); + showToast(_("STOP_RECORD")); + _node->publish(info, sizeof(Recorder_Info_t)); + return DataNode::RES_OK; +} + +bool DP_Recorder::onAutoRecord() +{ + Recorder_Info_t info; + info.active = true, + info.time = 1000; + _autoRecFinish = true; + + /* notify self to start record */ + return onNotify(&info); +} + +lv_fs_res_t DP_Recorder::writeFile(lv_fs_file_t* file_p, const char* str) +{ + // LV_LOG_USER(str); + + lv_fs_res_t res = lv_fs_write( + file_p, + str, + (uint32_t)strlen(str), + nullptr); + + return res; +} + +int DP_Recorder::makeDir(const char* path) +{ + auto dev = HAL::Manager()->getDevice("SdCard"); + if (!dev) { + return -1; + } + + auto ret = dev->ioctl(SDCARD_IOCMD_MKDIR, (void*)path, sizeof(path)); + + if (ret != DeviceObject::RES_OK) { + LV_LOG_ERROR("mkdir: %s failed %d", path, ret); + } + + return ret; +} + +int DP_Recorder::getTimeString(const char* format, char* buf, uint32_t size) +{ + HAL::Clock_Info_t clock; + int retval = -1; + if (_node->pull(_nodeClock, &clock, sizeof(clock)) == DataNode::RES_OK) { + retval = snprintf( + buf, + size, + format, + clock.year, + clock.month, + clock.day, + clock.hour, + clock.minute, + clock.second); + } + + return retval; +} + +void DP_Recorder::recPoint(const HAL::GNSS_Info_t* gnssInfo, const Slope_Info_t* slopeInfo) +{ + // LV_LOG_USER("Track recording..."); + + char timeBuf[64]; + int ret = getTimeString(RECORDER_GPX_TIME_FMT, timeBuf, sizeof(timeBuf)); + if (ret < 0) { + LV_LOG_WARN("cant't get time"); + return; + } + + auto altitude = slopeInfo ? slopeInfo->altitude : gnssInfo->altitude; + + _gpx.setEle(String(altitude, 2)); + _gpx.setTime(timeBuf); + + String gpxStr = _gpx.getPt( + GPX_TRKPT, + String(gnssInfo->longitude, 6), + String(gnssInfo->latitude, 6)); + + writeFile(&(_file), gpxStr.c_str()); +} + +void DP_Recorder::showToast(const char* msg) +{ + Toast_Info_t info; + info.txt = msg; + info.duration = 2000; + _node->notify("Toast", &info, sizeof(info)); +} + +bool DP_Recorder::recStart(uint16_t time) +{ + if (_recInfo.active) { + return false; + } + + HAL::GNSS_Info_t gnssInfo; + if (!(_node->pull("GNSS", &gnssInfo, sizeof(gnssInfo)) == DataNode::RES_OK + && gnssInfo.isVaild)) { + showToast(_("GNSS_NOT_READY")); + return false; + } + + Version_Info_t versionInfo; + if (_node->pull("Version", &versionInfo, sizeof(versionInfo)) != DataNode::RES_OK) { + return false; + } + + LV_LOG_USER("start"); + + HAL::Clock_Info_t clock; + if (_node->pull(_nodeClock, &clock, sizeof(clock)) != DataNode::RES_OK) { + LV_LOG_ERROR("cant't get clock"); + return false; + } + + char filepath[128]; + + /* create dir */ + lv_snprintf(filepath, sizeof(filepath), CONFIG_TRACK_RECORD_FILE_DIR_NAME "/" RECORDER_GPX_MONTH_FMT, + clock.year, clock.month); + if (makeDir(filepath) != DeviceObject::RES_OK) { + return false; + } + + /* create file */ + lv_snprintf(filepath, sizeof(filepath), RECORDER_GPX_FILE_NAME, + clock.year, clock.month, + clock.year, clock.month, clock.day, clock.hour, clock.minute, clock.second); + + LV_LOG_USER("Track file %s opening...", filepath); + + lv_fs_res_t res = lv_fs_open(&(_file), filepath, LV_FS_MODE_WR); + + if (res != LV_FS_RES_OK) { + LV_LOG_ERROR("failed %d", res); + showToast(_("OPEN_FILE_FAILED")); + return false; + } + + GPX* gpx = &_gpx; + lv_fs_file_t* file_p = &(_file); + + gpx->setMetaName(String(versionInfo.name) + " " + versionInfo.software); + gpx->setMetaDesc(versionInfo.website); + gpx->setName(filepath); + gpx->setDesc(""); + + writeFile(file_p, gpx->getOpen().c_str()); + writeFile(file_p, gpx->getMetaData().c_str()); + writeFile(file_p, gpx->getTrakOpen().c_str()); + writeFile(file_p, gpx->getInfo().c_str()); + writeFile(file_p, gpx->getTrakSegOpen().c_str()); + + _recInfo.active = true; + return true; +} + +bool DP_Recorder::recStop() +{ + if (!_recInfo.active) { + return false; + } + + GPX* gpx = &_gpx; + lv_fs_file_t* file_p = &(_file); + + writeFile(file_p, gpx->getTrakSegClose().c_str()); + writeFile(file_p, gpx->getTrakClose().c_str()); + writeFile(file_p, gpx->getClose().c_str()); + lv_fs_close(file_p); + + _recInfo.active = false; + return true; +} + +void DP_Recorder::setEnv(const char* value) +{ + _env.set("rec", value); +} + +int DP_Recorder::notifyFilter(bool active) +{ + TrackFilter_Info_t info; + info.active = active; + return _node->notify("TrackFilter", &info, sizeof(info)); +} + +DATA_PROC_DESCRIPTOR_DEF(Recorder) diff --git a/x_track/src/App/Service/DataProc/DP_SportStatus.cpp b/x_track/src/App/Service/DataProc/DP_SportStatus.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b2921a39134fdc21786bca90385e3262f037b33a --- /dev/null +++ b/x_track/src/App/Service/DataProc/DP_SportStatus.cpp @@ -0,0 +1,225 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Config/Config.h" +#include "DataProc.h" +#include "Service/HAL/HAL.h" +#include "Service/HAL/HAL_Log.h" +#include "Utils/Filters/Filters.h" +#include "Utils/Geo/Geo.h" + +/* clang-format off */ + +#define CALORIC_CORFFICIENT 0.5f + +#define CADENCE_TIMEOUT_MS (3000) // 20rpm + +#define MPS_TO_KPH(x) ((x) * 3.6f) +#define MPS_TO_MPH(x) ((x) * 2.2369362920544f) + +/* clang-format on */ + +using namespace DataProc; + +class DP_SportStatus { +public: + DP_SportStatus(DataNode* node); + +private: + DataNode* _node; + const DataNode* _nodeGNSS; + SportStatus_Info_t _sportStatus; + bool _isDistanceFirst; + double _preLongitude; + double _preLatitude; + +private: + int onEvent(DataNode::EventParam_t* param); + void onTimer(); + int onNotify(const SportStatus_Info_t* info); + double getDistanceOffset(const HAL::GNSS_Info_t* gnssInfo); +}; + +DP_SportStatus::DP_SportStatus(DataNode* node) + : _node(node) + , _nodeGNSS(nullptr) + , _isDistanceFirst(true) + , _preLongitude(0.0f) + , _preLatitude(0.0f) +{ + _nodeGNSS = node->subscribe("GNSS"); + +#define SPORT_STORAGE(MEMBER) storage.add(#MEMBER, &_sportStatus.MEMBER) + + Storage_Helper storage(node); + + storage.structStart("sportStatus"); + SPORT_STORAGE(totalDistance); + storage.addArray("totalTime", &_sportStatus.totalTime, sizeof(uint32_t), 2, STORAGE_TYPE::INT); + SPORT_STORAGE(speedMaxKph); + SPORT_STORAGE(weight); + SPORT_STORAGE(longitude); + SPORT_STORAGE(latitude); + storage.structEnd(); + + _sportStatus.weight = CONFIG_WEIGHT_DEFAULT; + _sportStatus.longitude = CONFIG_GNSS_LONGITUDE_DEFAULT; + _sportStatus.latitude = CONFIG_GNSS_LATITUDE_DEFAULT; + _sportStatus.lastTick = HAL::GetTick(); + + node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_SportStatus*)n->getUserData(); + return ctx->onEvent(param); + }, + DataNode::EVENT_PULL | DataNode::EVENT_TIMER | DataNode::EVENT_NOTIFY); + + node->startTimer(500); +} + +int DP_SportStatus::onEvent(DataNode::EventParam_t* param) +{ + switch (param->event) { + case DataNode::EVENT_NOTIFY: + if (param->size != sizeof(SportStatus_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + return onNotify((SportStatus_Info_t*)param->data_p); + + case DataNode::EVENT_PULL: + if (param->size != sizeof(SportStatus_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + memcpy(param->data_p, &_sportStatus, sizeof(SportStatus_Info_t)); + break; + + case DataNode::EVENT_TIMER: + onTimer(); + break; + + default: + break; + } + + return DataNode::RES_OK; +} + +void DP_SportStatus::onTimer() +{ + /* GPS */ + HAL::GNSS_Info_t gnssInfo; + if (_node->pull(_nodeGNSS, &gnssInfo, sizeof(gnssInfo)) != DataNode::RES_OK) { + return; + } + + uint32_t timeElaps = HAL::GetTickElaps(_sportStatus.lastTick); + + float speedKph = 0.0f; + bool isSignaLost = (gnssInfo.isVaild && (gnssInfo.satellites == 0)); + + /* Only valid latitude and longitude are recorded */ + if (gnssInfo.isVaild) { + _sportStatus.longitude = gnssInfo.longitude; + _sportStatus.latitude = gnssInfo.latitude; + _sportStatus.altitude = gnssInfo.altitude; + } + + if (gnssInfo.satellites >= 3) { + float spd = gnssInfo.speed; + speedKph = spd > 1 ? spd : 0; + } + + if (speedKph > 0.0f || isSignaLost) { + _sportStatus.singleTime += timeElaps; + _sportStatus.totalTime += timeElaps; + + if (speedKph > 0.0f) { + float dist = getDistanceOffset(&gnssInfo); + + _sportStatus.singleDistance += dist; + _sportStatus.totalDistance += dist; + + float meterPerSec = _sportStatus.singleDistance * 1000 / _sportStatus.singleTime; + _sportStatus.speedAvgKph = MPS_TO_KPH(meterPerSec); + + if (speedKph > _sportStatus.speedMaxKph) { + _sportStatus.speedMaxKph = speedKph; + } + + float calorie = speedKph * _sportStatus.weight * CALORIC_CORFFICIENT * timeElaps / 1000 / 3600; + _sportStatus.singleCalorie += calorie; + } + } + + _sportStatus.speedKph = speedKph; + + _sportStatus.lastTick = HAL::GetTick(); + + _node->publish(&_sportStatus, sizeof(SportStatus_Info_t)); +} + +int DP_SportStatus::onNotify(const SportStatus_Info_t* info) +{ + switch (info->cmd) { + case SPORT_STATUS_CMD::RESET_SINGLE: + _sportStatus.singleDistance = 0; + _sportStatus.singleCalorie = 0; + _sportStatus.singleTime = 0; + break; + case SPORT_STATUS_CMD::RESET_TOTAL: + _sportStatus.totalDistance = 0; + _sportStatus.totalTime = 0; + break; + case SPORT_STATUS_CMD::RESET_SPEED_MAX: + _sportStatus.speedMaxKph = 0; + break; + case SPORT_STATUS_CMD::SET_WEIGHT: + _sportStatus.weight = info->weight; + break; + default: + return DataNode::RES_PARAM_ERROR; + } + + return DataNode::RES_OK; +} + +double DP_SportStatus::getDistanceOffset(const HAL::GNSS_Info_t* gnssInfo) +{ + double offset = 0.0f; + + if (!_isDistanceFirst) { + offset = Geo::distanceBetween( + gnssInfo->latitude, + gnssInfo->longitude, + _preLatitude, + _preLongitude); + } else { + _isDistanceFirst = false; + } + + _preLongitude = gnssInfo->longitude; + _preLatitude = gnssInfo->latitude; + + return offset; +} + +DATA_PROC_DESCRIPTOR_DEF(SportStatus) diff --git a/x_track/src/App/Service/DataProc/DP_Storage.cpp b/x_track/src/App/Service/DataProc/DP_Storage.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2106d935a3fb8267facbd1a3ba5e32b010ab05de --- /dev/null +++ b/x_track/src/App/Service/DataProc/DP_Storage.cpp @@ -0,0 +1,222 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERDP_Storage + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Config/Config.h" +#include "DataProc.h" +#include "Service/HAL/HAL.h" +#include "Utils/StorageService/StorageService.h" +#include "lvgl/lvgl.h" + +/* System configuration file */ +#define SYSTEM_SAVE_PATH RESOURCE_MAKE_PATH("/SystemSave.json") +#define SYSTEM_SAVE_BACKUP_DIR RESOURCE_MAKE_PATH("/Backup") +#define SYSTEM_SAVE_BACKUP_FMT SYSTEM_SAVE_BACKUP_DIR "/SystemSave_%d%02d%02d_%02d%02d%02d.json" + +#define IS_STR_EQ(STR1, STR2) (strcmp(STR1, STR2) == 0) + +using namespace DataProc; + +class DP_Storage { +public: + DP_Storage(DataNode* node); + +public: + DataNode* _node; + const DataNode* _nodeGlobal; + Env_Helper _env; + StorageService _service; + DeviceObject* _dev; + +public: + int onEvent(DataNode::EventParam_t* param); + int onNotify(Storage_Info_t* info); + int onSdCardMount(bool isMount); + const char* getTimeString(const char* format, char* buf, size_t size); +}; + +DP_Storage::DP_Storage(DataNode* node) + : _node(node) + , _env(node) + , _service(SYSTEM_SAVE_PATH) +{ + _dev = HAL::Manager()->getDevice("SdCard"); + if (!_dev) { + return; + } + + /* create backup dir */ + const char* backupPath = SYSTEM_SAVE_BACKUP_DIR; + _dev->ioctl(SDCARD_IOCMD_MKDIR, (void*)backupPath, sizeof(backupPath)); + + node->subscribe("Clock"); + _nodeGlobal = node->subscribe("Global"); + + node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_Storage*)n->getUserData(); + return ctx->onEvent(param); + }, + DataNode::EVENT_PULL | DataNode::EVENT_NOTIFY | DataNode::EVENT_PUBLISH); +} + +int DP_Storage::onEvent(DataNode::EventParam_t* param) +{ + switch (param->event) { + case DataNode::EVENT_PULL: { + if (param->size != sizeof(Storage_Device_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + + Storage_Device_Info_t* info = (Storage_Device_Info_t*)param->data_p; + HAL::SdCard_Info_t sdInfo; + if (_dev->read(&sdInfo, sizeof(sdInfo)) != sizeof(sdInfo)) { + return DataNode::RES_NO_DATA; + } + + info->isDetect = sdInfo.isInsert; + info->isActive = sdInfo.isActive; + info->totalSize = sdInfo.totalCapacity; + info->freeSize = sdInfo.freeCapacity; + } break; + + case DataNode::EVENT_NOTIFY: { + if (param->size != sizeof(Storage_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + + Storage_Info_t* info = (Storage_Info_t*)param->data_p; + return onNotify(info); + } break; + + case DataNode::EVENT_PUBLISH: { + if (param->tran == _env) { + auto envInfo = (DataProc::Env_Info_t*)param->data_p; + if (IS_STR_EQ(envInfo->key, "storage")) { + Storage_Info_t info; + if (IS_STR_EQ(envInfo->value, "save")) { + info.cmd = STORAGE_CMD::SAVE; + } else if (IS_STR_EQ(envInfo->value, "load")) { + info.cmd = STORAGE_CMD::LOAD; + } else { + return DataNode::RES_PARAM_ERROR; + } + + return onNotify(&info); + } + } + + if (param->tran == _nodeGlobal) { + auto globalInfo = (const DataProc::Global_Info_t*)param->data_p; + + /* Load data after initialization */ + if (globalInfo->event == DataProc::GLOBAL_EVENT::DATA_PROC_INIT_FINISHED) { + Storage_Info_t info; + info.cmd = STORAGE_CMD::LOAD; + + /* Unsubscribe global data node */ + _node->unsubscribe("Global"); + _nodeGlobal = nullptr; + return onNotify(&info); + } + } + } break; + + default: + break; + } + + return DataNode::RES_OK; +} + +int DP_Storage::onNotify(Storage_Info_t* info) +{ + switch (info->cmd) { + case STORAGE_CMD::LOAD: { + if (_service.load()) { + _node->publish(info, sizeof(Storage_Info_t)); + } else { + LV_LOG_WARN("Load " SYSTEM_SAVE_PATH " error"); + } + } break; + + case STORAGE_CMD::SAVE: { + bool ret = _service.save(); + LV_LOG_USER("Saving file '" SYSTEM_SAVE_PATH "', ret: %d", ret); + + char buf[128]; + const char* timeStr = getTimeString(SYSTEM_SAVE_BACKUP_FMT, buf, sizeof(buf)); + ret = _service.save(timeStr); + LV_LOG_USER("Saving backup file '%s', ret: %d", timeStr, ret); + } break; + + case STORAGE_CMD::ADD: + _service.add( + info->key, + info->value, + info->size, + (StorageService::TYPE)info->type); + break; + case STORAGE_CMD::MOUNT: + case STORAGE_CMD::UNMOUNT: + return onSdCardMount(info->cmd == STORAGE_CMD::MOUNT); + + default: + break; + } + + return DataNode::RES_OK; +} + +int DP_Storage::onSdCardMount(bool isMount) +{ + int ret = isMount ? _dev->ioctl(SDCARD_IOCMD_MOUNT) : _dev->ioctl(SDCARD_IOCMD_UNMOUNT); + LV_LOG_WARN("isMount: %d, ret = %d", isMount, ret); + + return (ret == DeviceObject::RES_OK) ? DataNode::RES_OK : DataNode::RES_UNKNOWN; +} + +const char* DP_Storage::getTimeString(const char* format, char* buf, size_t size) +{ + HAL::Clock_Info_t clock; + if (_node->pull("Clock", &clock, sizeof(clock)) != DataNode::RES_OK) { + return nullptr; + } + + int retval = lv_snprintf( + buf, + size, + format, + clock.year, + clock.month, + clock.day, + clock.hour, + clock.minute, + clock.second); + + if (retval < 0 || retval >= (int)size) { + return nullptr; + } + + return buf; +} + +DATA_PROC_DESCRIPTOR_DEF(Storage) diff --git a/x_track/src/App/Service/DataProc/DP_SunRise.cpp b/x_track/src/App/Service/DataProc/DP_SunRise.cpp new file mode 100644 index 0000000000000000000000000000000000000000..89bbd9578530873e2127b3cd20490d710f3dd175 --- /dev/null +++ b/x_track/src/App/Service/DataProc/DP_SunRise.cpp @@ -0,0 +1,158 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "DataProc.h" +#include "Utils/SunRiseCalc/SunRiseCalc.h" + +#define IS_STR_EQ(STR1, STR2) (strcmp(STR1, STR2) == 0) + +using namespace DataProc; + +class DP_SunRise { +public: + DP_SunRise(DataNode* node); + +private: + DataNode* _node; + const DataNode* _nodeClock; + Env_Helper _env; + SunRise_Info_t _info; + +private: + int onEvent(DataNode::EventParam_t* param); + int onClockEvent(const HAL::Clock_Info_t* clock); + SunRise_Info_t getInfo(const HAL::Clock_Info_t* clock); + SUNRISE_STATE compare(const HAL::Clock_Info_t* clock, const SunRise_Info_t* sunRise); +}; + +DP_SunRise::DP_SunRise(DataNode* node) + : _node(node) + , _env(node) +{ + node->subscribe("SportStatus"); + _nodeClock = node->subscribe("Clock"); + + _node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_SunRise*)n->getUserData(); + return ctx->onEvent(param); + }, + DataNode::EVENT_PULL | DataNode::EVENT_PUBLISH); +} + +int DP_SunRise::onEvent(DataNode::EventParam_t* param) +{ + switch (param->event) { + case DataNode::EVENT_PULL: { + if (param->size != sizeof(SunRise_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + + auto info = (SunRise_Info_t*)param->data_p; + + if (_info.state != SUNRISE_STATE::UNKNOWN) { + /* use the cached value */ + *info = _info; + return DataNode::RES_OK; + } + + HAL::Clock_Info_t clockInfo; + if (_node->pull(_nodeClock, &clockInfo, sizeof(clockInfo)) != DataNode::RES_OK) { + return DataNode::RES_NO_DATA; + } + + *info = getInfo(&clockInfo); + } break; + + case DataNode::EVENT_PUBLISH: + if (param->tran == _nodeClock) { + return onClockEvent((HAL::Clock_Info_t*)param->data_p); + } else if (param->tran == _env) { + auto envInfo = (const Env_Info_t*)param->data_p; + + /* when timezone changed, reset the state */ + if (IS_STR_EQ(envInfo->key, "timeZone")) { + _info.state = SUNRISE_STATE::UNKNOWN; + } + } + break; + + default: + return DataNode::RES_UNSUPPORTED_REQUEST; + } + + return DataNode::RES_OK; +} + +int DP_SunRise::onClockEvent(const HAL::Clock_Info_t* clock) +{ + if (_info.state == SUNRISE_STATE::UNKNOWN) { + _info = getInfo(clock); + _node->publish(&_info, sizeof(_info)); + return DataNode::RES_OK; + } + + /* compare the state */ + auto newState = compare(clock, &_info); + if (newState != _info.state) { + _info.state = newState; + _node->publish(&_info, sizeof(_info)); + } + + return DataNode::RES_OK; +} + +SunRise_Info_t DP_SunRise::getInfo(const HAL::Clock_Info_t* clock) +{ + SunRise_Info_t result; + + SportStatus_Info_t sportStatus; + if (_node->pull("SportStatus", &sportStatus, sizeof(sportStatus)) == DataNode::RES_OK) { + SunRiseSunSetCalculator( + _env.getInt("timeZone"), + clock->year, + clock->month, + clock->day, + sportStatus.longitude, + sportStatus.latitude, + &result.sunriseHour, + &result.sunriseMinute, + &result.sunsetHour, + &result.sunsetMinute); + + result.state = compare(clock, &result); + } + + return result; +} + +SUNRISE_STATE DP_SunRise::compare(const HAL::Clock_Info_t* clock, const SunRise_Info_t* sunRise) +{ + if ((clock->hour > sunRise->sunriseHour || (clock->hour == sunRise->sunriseHour && clock->minute >= sunRise->sunriseMinute)) + && (clock->hour < sunRise->sunsetHour || (clock->hour == sunRise->sunsetHour && clock->minute < sunRise->sunsetMinute))) { + return SUNRISE_STATE::DAY; + } + + return SUNRISE_STATE::NIGHT; +} + +DATA_PROC_DESCRIPTOR_DEF(SunRise) diff --git a/x_track/src/App/Service/DataProc/DP_Theme.cpp b/x_track/src/App/Service/DataProc/DP_Theme.cpp new file mode 100644 index 0000000000000000000000000000000000000000..342f4c6891a509e334b547a15a38bddbca73935a --- /dev/null +++ b/x_track/src/App/Service/DataProc/DP_Theme.cpp @@ -0,0 +1,184 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "DataProc.h" +#include "UI/Resource/ResourcePool.h" +#include "lvgl/src/lvgl_private.h" +#include <cstdlib> + +#define THEME_DARK_COLOR_PRIMARY lv_color_hex(0xEB5E00) +#define THEME_DARK_COLOR_SECONDARY lv_color_hex(0xEA5455) +#define THEME_LIGHT_COLOR_PRIMARY lv_palette_main(LV_PALETTE_BLUE) +#define THEME_LIGHT_COLOR_SECONDARY lv_palette_main(LV_PALETTE_RED) + +#define THEME_NAME "theme" +#define THEME_DARK_NAME "dark" +#define THEME_LIGHT_NAME "light" +#define THEME_AUTO_NAME "auto" +#define DISP_ROTATION_NAME "disp-rotation" + +#define THEME_FONT_SMALL "<12>regular" +#define THEME_FONT_NORMAL "<16>regular" +#define THEME_FONT_LARGE "<20>regular" + +#define THEME_FONTAWESOME_SMALL "<12>awesome" +#define THEME_FONTAWESOME_NORMAL "<16>awesome" +#define THEME_FONTAWESOME_LARGE "<20>awesome" + +#define IS_STR_EQ(STR1, STR2) (strcmp(STR1, STR2) == 0) + +using namespace DataProc; + +class DP_Theme { +public: + DP_Theme(DataNode* node); + +private: + DataNode* _node; + const DataNode* _nodeStorage; + const DataNode* _nodeSunRise; + Env_Helper _env; + char _themeName[8]; + int _dispRotation; + int8_t _isDark; + ResourcePool::Font _fontSmall; + ResourcePool::Font _fontNormal; + ResourcePool::Font _fontLarge; + ResourcePool::Font _fontAwesomeSmall; + ResourcePool::Font _fontAwesomeNormal; + ResourcePool::Font _fontAwesomeLarge; + lv_font_t _fontSmallHandle; + lv_font_t _fontNormalHandle; + lv_font_t _fontLargeHandle; + +private: + int onEvent(DataNode::EventParam_t* param); + void updateTheme(bool isDark); +}; + +DP_Theme::DP_Theme(DataNode* node) + : _node(node) + , _nodeStorage(nullptr) + , _nodeSunRise(nullptr) + , _env(node) + , _themeName(THEME_DARK_NAME) + , _isDark(-1) + , _fontSmall(THEME_FONT_SMALL) + , _fontNormal(THEME_FONT_NORMAL) + , _fontLarge(THEME_FONT_LARGE) + , _fontAwesomeSmall(THEME_FONTAWESOME_SMALL) + , _fontAwesomeNormal(THEME_FONTAWESOME_NORMAL) + , _fontAwesomeLarge(THEME_FONTAWESOME_LARGE) +{ + _nodeStorage = node->subscribe("Storage"); + _nodeSunRise = node->subscribe("SunRise"); + + Storage_Helper storage(node); + storage.add(THEME_NAME, _themeName, sizeof(_themeName), STORAGE_TYPE::STRING); + storage.add(DISP_ROTATION_NAME, &_dispRotation, sizeof(_dispRotation), STORAGE_TYPE::INT); + + node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_Theme*)n->getUserData(); + return ctx->onEvent(param); + }, + DataNode::EVENT_PUBLISH); + + lv_display_add_event_cb( + lv_display_get_default(), + [](lv_event_t* e) { + auto self = (DP_Theme*)lv_event_get_user_data(e); + auto disp = (lv_disp_t*)lv_event_get_current_target(e); + self->_dispRotation = lv_disp_get_rotation(disp); + }, + LV_EVENT_RESOLUTION_CHANGED, + this); + + /* Initialize font handles */ + _fontSmallHandle = *_fontSmall; + _fontSmallHandle.fallback = _fontAwesomeSmall; + _fontNormalHandle = *_fontNormal; + _fontNormalHandle.fallback = _fontAwesomeNormal; + _fontLargeHandle = *_fontLarge; + _fontLargeHandle.fallback = _fontAwesomeLarge; + + updateTheme(true); +} + +int DP_Theme::onEvent(DataNode::EventParam_t* param) +{ + if (param->tran == _env) { + auto info = (const Env_Info_t*)param->data_p; + + if (IS_STR_EQ(info->key, THEME_NAME)) { + if (info->value != _themeName) { + strncpy(_themeName, info->value, sizeof(_themeName) - 1); + } + + bool isDark; + if (IS_STR_EQ(info->value, THEME_AUTO_NAME)) { + SunRise_Info_t sunRiseInfo; + _node->pull(_nodeSunRise, &sunRiseInfo, sizeof(sunRiseInfo)); + isDark = sunRiseInfo.state == SUNRISE_STATE::NIGHT; + } else { + isDark = IS_STR_EQ(info->value, THEME_DARK_NAME); + } + updateTheme(isDark); + } + } else if (param->tran == _nodeStorage) { + auto info = (const Storage_Info_t*)param->data_p; + + if (info->cmd != STORAGE_CMD::LOAD) { + return DataNode::RES_UNSUPPORTED_REQUEST; + } + + _env.set(THEME_NAME, _themeName); + lv_disp_set_rotation(nullptr, (lv_disp_rotation_t)_dispRotation); + } else if (param->tran == _nodeSunRise) { + if (IS_STR_EQ(_themeName, THEME_AUTO_NAME)) { + auto info = (const SunRise_Info_t*)param->data_p; + updateTheme(info->state == SUNRISE_STATE::NIGHT); + } + } + + return DataNode::RES_OK; +} + +void DP_Theme::updateTheme(bool isDark) +{ + /* Update theme only when the theme is changed */ + if (_isDark == isDark) { + return; + } + + _isDark = isDark; + + lv_color_t color_primary = isDark ? THEME_DARK_COLOR_PRIMARY : THEME_LIGHT_COLOR_PRIMARY; + lv_color_t color_secondary = isDark ? THEME_DARK_COLOR_SECONDARY : THEME_LIGHT_COLOR_SECONDARY; + + lv_theme_t* def_theme = lv_theme_default_init(nullptr, color_primary, color_secondary, isDark, LV_FONT_DEFAULT); + def_theme->font_small = &_fontSmallHandle; + def_theme->font_normal = &_fontNormalHandle; + def_theme->font_large = &_fontLargeHandle; +} + +DATA_PROC_DESCRIPTOR_DEF(Theme) diff --git a/x_track/src/App/Service/DataProc/DP_Toast.cpp b/x_track/src/App/Service/DataProc/DP_Toast.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8e90ab2e83a5f44cc96c38686f85aa102fc39def --- /dev/null +++ b/x_track/src/App/Service/DataProc/DP_Toast.cpp @@ -0,0 +1,102 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Config/Config.h" +#include "DataProc.h" +#include "Frameworks/PageManager/PageManager.h" +#include "UI/Resource/ResourcePool.h" +#include "Utils/lv_toast/lv_toast.h" + +using namespace DataProc; + +class DP_Toast { +public: + DP_Toast(DataNode* node); + +private: + const DataNode* _nodeGlobal; + lv_obj_t* _toast; + lv_style_t _style; + ResourcePool::Font _font; + +private: + int onEvent(DataNode::EventParam_t* param); +}; + +DP_Toast::DP_Toast(DataNode* node) + : _toast(nullptr) + , _font(16, "bold") +{ + _nodeGlobal = node->subscribe("Global"); + + lv_style_init(&_style); + lv_style_set_radius(&_style, LV_RADIUS_CIRCLE); + lv_style_set_margin_top(&_style, 10); + lv_style_set_bg_color(&_style, lv_color_black()); + lv_style_set_bg_opa(&_style, LV_OPA_60); + lv_style_set_border_width(&_style, 0); + lv_style_set_text_color(&_style, lv_color_white()); + lv_style_set_text_font(&_style, _font); + + _toast = lv_toast_create(lv_layer_top()); + lv_toast_set_cont_style(_toast, &_style); + + node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_Toast*)n->getUserData(); + return ctx->onEvent(param); + }, + DataNode::EVENT_NOTIFY | DataNode::EVENT_PUBLISH); +} + +int DP_Toast::onEvent(DataNode::EventParam_t* param) +{ + switch (param->event) { + case DataNode::EVENT_NOTIFY: { + if (param->size != sizeof(Toast_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + + auto info = (const Toast_Info_t*)param->data_p; + lv_toast_show_text(_toast, info->txt, info->duration); + } break; + + case DataNode::EVENT_PUBLISH: { + if (param->tran == _nodeGlobal) { + auto info = (const Global_Info_t*)param->data_p; + if (info->event == GLOBAL_EVENT::PAGE_MANAGER_INIT_FINISHED) { + auto manager = (PageManager*)info->param; + + /* Change the parent of the toast to the top layer of the page manager */ + lv_obj_set_parent(_toast, manager->getLayerTop()); + } + } + } break; + + default: + return DataNode::RES_UNSUPPORTED_REQUEST; + } + + return DataNode::RES_OK; +} + +DATA_PROC_DESCRIPTOR_DEF(Toast) diff --git a/x_track/src/App/Service/DataProc/DP_TrackFilter.cpp b/x_track/src/App/Service/DataProc/DP_TrackFilter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6ee0174e75c655f1625299514e64755ffc51a483 --- /dev/null +++ b/x_track/src/App/Service/DataProc/DP_TrackFilter.cpp @@ -0,0 +1,170 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "DataProc.h" +#include "Utils/MapConv/MapConv.h" +#include "Utils/PointContainer/PointContainer.h" +#include "Utils/TrackPointFilter/TrackPointFilter.h" +#include "lvgl/lvgl.h" +#include <vector> + +#define FILTER_LEVEL 16 + +using namespace DataProc; + +class DP_TrackFilter { +public: + DP_TrackFilter(DataNode* node); + +private: + DataNode* _node; + const DataNode* _nodeGNSS; + const DataNode* _nodeMapInfo; + PointContainer* _pointContainer; + bool _isActive; + MapConv _mapConv; + TrackPointFilter _pointFilter; + +private: + int onEvent(DataNode::EventParam_t* param); + bool onNotify(const TrackFilter_Info_t* info); + void onGNSSPublish(const HAL::GNSS_Info_t* gnssInfo); +}; + +DP_TrackFilter::DP_TrackFilter(DataNode* node) + : _node(node) + , _nodeGNSS(nullptr) + , _pointContainer(nullptr) + , _isActive(false) + , _mapConv(true, FILTER_LEVEL) +{ + _nodeGNSS = node->subscribe("GNSS"); + if (!_nodeGNSS) { + return; + } + + _nodeMapInfo = node->subscribe("MapInfo"); + + node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_TrackFilter*)n->getUserData(); + return ctx->onEvent(param); + }, + DataNode::EVENT_PUBLISH | DataNode::EVENT_PULL | DataNode::EVENT_NOTIFY); +} + +int DP_TrackFilter::onEvent(DataNode::EventParam_t* param) +{ + if (param->tran == _nodeGNSS) { + onGNSSPublish((HAL::GNSS_Info_t*)param->data_p); + return DataNode::RES_OK; + } + + if (param->size != sizeof(TrackFilter_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + + switch (param->event) { + case DataNode::EVENT_PULL: { + TrackFilter_Info_t* info = (TrackFilter_Info_t*)param->data_p; + info->active = _isActive; + info->level = FILTER_LEVEL; + info->pointCont = _pointContainer; + } break; + case DataNode::EVENT_NOTIFY: + if (onNotify((TrackFilter_Info_t*)param->data_p)) { + + /* publish status */ + TrackFilter_Info_t info; + info.active = _isActive; + info.level = FILTER_LEVEL; + info.pointCont = _pointContainer; + _node->publish(&info, sizeof(info)); + } + break; + + default: + break; + } + + return DataNode::RES_OK; +} + +bool DP_TrackFilter::onNotify(const TrackFilter_Info_t* info) +{ + if (info->active) { + /* check already started */ + if (_pointContainer) { + LV_LOG_WARN("Track filter already started"); + return false; + } + + /* start */ + _pointContainer = new PointContainer; + _pointFilter.reset(); + _isActive = true; + + /* get map info */ + Map_Info_t mapInfo; + if (_node->pull(_nodeMapInfo, &mapInfo, sizeof(mapInfo)) == DataNode::RES_OK) { + _mapConv.setCoordTransformEnable(mapInfo.coordTrans); + } + + LV_LOG_USER("Track filter start"); + return true; + } + + if (!_pointContainer) { + LV_LOG_WARN("NOT started"); + return false; + } + + _isActive = false; + delete _pointContainer; + _pointContainer = nullptr; + + uint32_t sum = 0, output = 0; + _pointFilter.getCounts(&sum, &output); + LV_LOG_USER( + "Track filter stop, " + "filted(%" LV_PRIu32 "%%): sum = %" LV_PRIu32 ", output = %" LV_PRIu32, + sum ? (100 - output * 100 / sum) : 0, + sum, + output); + + return true; +} + +void DP_TrackFilter::onGNSSPublish(const HAL::GNSS_Info_t* gnssInfo) +{ + if (!_isActive) { + return; + } + + MapConv::Point_t point = _mapConv.getCoordinate(gnssInfo->longitude, gnssInfo->latitude); + + if (_pointFilter.pushPoint(point.x, point.y)) { + _pointContainer->pushPoint(point.x, point.y); + } +} + +DATA_PROC_DESCRIPTOR_DEF(TrackFilter); diff --git a/x_track/src/App/Service/DataProc/DP_Version.cpp b/x_track/src/App/Service/DataProc/DP_Version.cpp new file mode 100644 index 0000000000000000000000000000000000000000..014d113f90ca7d8596f1fee4d5aa80f68cdab237 --- /dev/null +++ b/x_track/src/App/Service/DataProc/DP_Version.cpp @@ -0,0 +1,125 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "../HAL/HAL_Log.h" +#include "DataProc.h" +#include "Version.h" + +/* clang-format off */ + +/* Number to string macro */ +#define _VERSION_NUM_TO_STR_(n) #n +#define VERSION_NUM_TO_STR(n) _VERSION_NUM_TO_STR_(n) + +/* Compiler Version */ +#if defined(_MSC_FULL_VER) +# define VERSION_COMPILER "MSVC v" VERSION_NUM_TO_STR(_MSC_FULL_VER) +#elif defined(__ARMCC_VERSION) +# define VERSION_COMPILER "ARMCC v" VERSION_NUM_TO_STR(__ARMCC_VERSION) +#elif defined(__GNUC__) +# define VERSION_COMPILER "GCC v"\ + VERSION_NUM_TO_STR(__GNUC__)\ + "."\ + VERSION_NUM_TO_STR(__GNUC_MINOR__)\ + "."\ + VERSION_NUM_TO_STR(__GNUC_PATCHLEVEL__) +#else +# define VERSION_COMPILER "UNKNOW" +#endif + +/* LVGL Version */ +#include "lvgl/lvgl.h" +#define VERSION_GRAPHICS "LVGL v"\ + VERSION_NUM_TO_STR(LVGL_VERSION_MAJOR)\ + "."\ + VERSION_NUM_TO_STR(LVGL_VERSION_MINOR)\ + "."\ + VERSION_NUM_TO_STR(LVGL_VERSION_PATCH)\ + " "\ + LVGL_VERSION_INFO + +/* clang-format on */ + +using namespace DataProc; + +class DP_Version { +public: + DP_Version(DataNode* node); + +private: + int onEvent(DataNode::EventParam_t* param); + void getInfo(Version_Info_t* info); + void dumpInfo(const Version_Info_t* info); +}; + +DP_Version::DP_Version(DataNode* node) +{ + node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_Version*)n->getUserData(); + return ctx->onEvent(param); + }, + DataNode::EVENT_PULL); + + Version_Info_t version; + getInfo(&version); + dumpInfo(&version); +} + +int DP_Version::onEvent(DataNode::EventParam_t* param) +{ + if (param->size != sizeof(Version_Info_t)) { + return DataNode::RES_SIZE_MISMATCH; + } + + auto info = (Version_Info_t*)param->data_p; + getInfo(info); + + return DataNode::RES_OK; +} + +void DP_Version::getInfo(Version_Info_t* info) +{ + info->name = VERSION_FIRMWARE_NAME; + info->software = VERSION_SOFTWARE; + info->hardware = VERSION_HARDWARE; + info->author = VERSION_AUTHOR_NAME; + info->website = VERSION_WEBSITE; + info->graphics = VERSION_GRAPHICS; + info->compiler = VERSION_COMPILER; + info->buildDate = __DATE__; + info->buildTime = __TIME__; +} + +void DP_Version::dumpInfo(const Version_Info_t* info) +{ + HAL_LOG_INFO("Firmware: %s", info->name); + HAL_LOG_INFO("Software: %s", info->software); + HAL_LOG_INFO("Hardware: %s", info->hardware); + HAL_LOG_INFO("Author: %s", info->author); + HAL_LOG_INFO("Website: %s", info->website); + HAL_LOG_INFO("Graphics: %s", info->graphics); + HAL_LOG_INFO("Compiler: %s", info->compiler); + HAL_LOG_INFO("Build Time: %s %s", info->buildDate, info->buildTime); +} + +DATA_PROC_DESCRIPTOR_DEF(Version) diff --git a/x_track/src/App/Service/DataProc/DP_i18n.cpp b/x_track/src/App/Service/DataProc/DP_i18n.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e644648bcfdfb4efb9e9c119696aede754c10633 --- /dev/null +++ b/x_track/src/App/Service/DataProc/DP_i18n.cpp @@ -0,0 +1,100 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Config/Config.h" +#include "DataProc.h" +#include "Service/i18n/lv_i18n.h" +#include "lvgl/lvgl.h" + +#define IS_STR_EQ(STR1, STR2) (strcmp(STR1, STR2) == 0) +#define LANGUAGE_STR "language" + +using namespace DataProc; + +class DP_i18n { +public: + DP_i18n(DataNode* node); + +private: + DataNode* _node; + const DataNode* _nodeEnv; + const DataNode* _nodeStorage; + char _language[8]; + +private: + int onEvent(DataNode::EventParam_t* param); +}; + +DP_i18n::DP_i18n(DataNode* node) + : _node(node) + , _nodeEnv(nullptr) + , _language { CONFIG_LANGUAGE_DEFAULT } +{ + lv_i18n_init(lv_i18n_language_pack); + + _nodeEnv = node->subscribe("Env"); + _nodeStorage = node->subscribe("Storage"); + node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_i18n*)n->getUserData(); + return ctx->onEvent(param); + }, + DataNode::EVENT_PUBLISH); + + Storage_Helper storage(node); + storage.add(LANGUAGE_STR, _language, sizeof(_language), STORAGE_TYPE::STRING); + + if (lv_i18n_set_locale(_language) != 0) { + LV_LOG_ERROR("set locale '%s' failed", _language); + } +} + +int DP_i18n::onEvent(DataNode::EventParam_t* param) +{ + if (param->tran == _nodeEnv) { + auto info = (Env_Info_t*)param->data_p; + if (IS_STR_EQ(info->key, LANGUAGE_STR)) { + if (lv_i18n_set_locale(info->value) != 0) { + LV_LOG_ERROR("set locale '%s' failed", info->value); + return DataNode::RES_PARAM_ERROR; + } + + /* fix strncpy-param-overlap */ + if (info->value != _language) { + strncpy(_language, info->value, sizeof(_language) - 1); + } + LV_LOG_USER("set locale to %s", info->value); + } + } else if (param->tran == _nodeStorage) { + auto info = (Storage_Info_t*)param->data_p; + /* load language */ + if (info->cmd == STORAGE_CMD::LOAD) { + Env_Info_t envInfo; + envInfo.key = LANGUAGE_STR; + envInfo.value = _language; + _node->notify(_nodeEnv, &envInfo, sizeof(Env_Info_t)); + } + } + return DataNode::RES_OK; +} + +DATA_PROC_DESCRIPTOR_DEF(i18n) diff --git a/x_track/src/App/Service/DataProc/DataProc.cpp b/x_track/src/App/Service/DataProc/DataProc.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ca5d2c2a5608c979229488a7b240723a6f724838 --- /dev/null +++ b/x_track/src/App/Service/DataProc/DataProc.cpp @@ -0,0 +1,53 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "DataProc.h" +#include "Service/HAL/HAL.h" + +static DataBroker* DataProcBroker = nullptr; + +void DataProc_Init(DataBroker* broker) +{ + DataProcBroker = broker; + + broker->initTimerManager(HAL::GetTick); + +/* Create DataNode */ +#define DP_DEF(NAME) \ + DataNode* node##NAME = new DataNode(#NAME, broker); +#include "DataProc_NodeList.inc" +#undef DP_DEF + +/* Init DataNode */ +#define DP_DEF(NAME) \ + do { \ + void DP_##NAME##_Init(DataNode* node); \ + DP_##NAME##_Init(node##NAME); \ + } while (0) +#include "DataProc_NodeList.inc" +#undef DP_DEF +} + +DataBroker* DataProc::broker() +{ + return DataProcBroker; +} diff --git a/x_track/src/App/Service/DataProc/DataProc.h b/x_track/src/App/Service/DataProc/DataProc.h new file mode 100644 index 0000000000000000000000000000000000000000..c4a7a158d1ff8fcd300603cc199f9cb6586eb098 --- /dev/null +++ b/x_track/src/App/Service/DataProc/DataProc.h @@ -0,0 +1,47 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_H +#define __DATA_PROC_H + +#include "DataProc_Def.h" +#include "DataProc_Helper.h" +#include "Frameworks/DataBroker/DataBroker.h" +#include "Service/HAL/HAL_Def.h" +#include <cstring> + +#define DATA_PROC_DESCRIPTOR_DEF(NAME) \ + void DP_##NAME##_Init(DataNode* node) \ + { \ + static DP_##NAME ctx(node); \ + node->setUserData(&ctx); \ + } + +void DataProc_Init(DataBroker* broker); + +namespace DataProc { + +DataBroker* broker(); + +} + +#endif diff --git a/x_track/src/App/Service/DataProc/DataProc_Def.h b/x_track/src/App/Service/DataProc/DataProc_Def.h new file mode 100644 index 0000000000000000000000000000000000000000..af9fbc5f5a2cc204f6d33fef52e8fd92f96bdf3f --- /dev/null +++ b/x_track/src/App/Service/DataProc/DataProc_Def.h @@ -0,0 +1,43 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_DEF_H +#define __DATA_PROC_DEF_H + +#include "Def/DP_Backlight.h" +#include "Def/DP_Env.h" +#include "Def/DP_FeedbackGen.h" +#include "Def/DP_GNSS.h" +#include "Def/DP_Global.h" +#include "Def/DP_MapInfo.h" +#include "Def/DP_PageNavi.h" +#include "Def/DP_Power.h" +#include "Def/DP_Recorder.h" +#include "Def/DP_Slope.h" +#include "Def/DP_SportStatus.h" +#include "Def/DP_Storage.h" +#include "Def/DP_SunRise.h" +#include "Def/DP_Toast.h" +#include "Def/DP_TrackFilter.h" +#include "Def/DP_Version.h" + +#endif // __DATA_PROC_DEF_H diff --git a/x_track/src/App/Service/DataProc/DataProc_Helper.h b/x_track/src/App/Service/DataProc/DataProc_Helper.h new file mode 100644 index 0000000000000000000000000000000000000000..2dd8691cca87d73dc1f9f0c1e4c0cc26497d17b0 --- /dev/null +++ b/x_track/src/App/Service/DataProc/DataProc_Helper.h @@ -0,0 +1,34 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_HELPER_H +#define __DATA_PROC_HELPER_H + +#include "Helper/Env_Helper.h" +#include "Helper/FeedbackGen_Helper.h" +#include "Helper/Global_Helper.h" +#include "Helper/LED_Helper.h" +#include "Helper/MsgBox_Helper.h" +#include "Helper/Storage_Helper.h" +#include "Helper/Toast_Helper.h" + +#endif // __DATA_PROC_HELPER_H diff --git a/x_track/src/App/Service/DataProc/DataProc_NodeList.inc b/x_track/src/App/Service/DataProc/DataProc_NodeList.inc new file mode 100644 index 0000000000000000000000000000000000000000..3882621e48b91b83d9efed42c080e2bdb2072973 --- /dev/null +++ b/x_track/src/App/Service/DataProc/DataProc_NodeList.inc @@ -0,0 +1,20 @@ + +/* Data processing node list */ + +DP_DEF(Global); +DP_DEF(Storage); +DP_DEF(i18n); +DP_DEF(Backlight); +DP_DEF(Clock); +DP_DEF(Env); +DP_DEF(Power); +DP_DEF(GNSS); +DP_DEF(MapInfo); +DP_DEF(Recorder); +DP_DEF(SportStatus); +DP_DEF(PageNavi); +DP_DEF(Toast); +DP_DEF(TrackFilter); +DP_DEF(Version); +DP_DEF(Theme); +DP_DEF(SunRise); diff --git a/x_track/src/App/Service/DataProc/Def/DP_Backlight.h b/x_track/src/App/Service/DataProc/Def/DP_Backlight.h new file mode 100644 index 0000000000000000000000000000000000000000..219b4763a4dfe9e99d76a15efe1382e8fe9bab12 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_Backlight.h @@ -0,0 +1,50 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_BACKLIGHT_DEF_H +#define __DATA_PROC_BACKLIGHT_DEF_H + +#include <cstdint> + +namespace DataProc { + +/* Backlight */ + +typedef struct Backlight_Info { + Backlight_Info() + : brightness(0) + , brightnessDay(-1) + , brightnessNight(-1) + , anim(false) + , disable(false) + { + } + int brightness; + int brightnessDay; + int brightnessNight; + bool anim; + bool disable; +} Backlight_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_BACKLIGHT_DEF_H \ No newline at end of file diff --git a/x_track/src/App/Service/DataProc/Def/DP_Env.h b/x_track/src/App/Service/DataProc/Def/DP_Env.h new file mode 100644 index 0000000000000000000000000000000000000000..d0706ab1ead8753367eb2ce482c0366442cdb4bf --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_Env.h @@ -0,0 +1,42 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_ENV_DEF_H +#define __DATA_PROC_ENV_DEF_H + +#include <cstdint> + +namespace DataProc { + +typedef struct Env_Info { + Env_Info() + : key(nullptr) + , value(nullptr) + { + } + const char* key; + const char* value; +} Env_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_ENV_DEF_H diff --git a/x_track/src/App/Service/DataProc/Def/DP_FeedbackGen.h b/x_track/src/App/Service/DataProc/Def/DP_FeedbackGen.h new file mode 100644 index 0000000000000000000000000000000000000000..ab35cd49b0a9fa102a45628a61b65259a85aef18 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_FeedbackGen.h @@ -0,0 +1,68 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_FEEDBACK_GEN_DEF_H +#define __DATA_PROC_FEEDBACK_GEN_DEF_H + +#include <cstdint> + +namespace DataProc { + +/* https://developer.apple.com/documentation/uikit/uifeedbackgenerator */ + +enum class FEEDBACK_GEN_CMD { + ENABLE, + PREPARE, + TRIGGER, + FINALIZE, + CALIBRATE, +}; + +enum class FEEDBACK_GEN_EFFECT { + NONE, + IMPACT_LIGHT, + IMPACT_MEDIUM, + IMPACT_HEAVY, + SELECTION, + NOTIFICATION_ERROR, + NOTIFICATION_SUCCESS, + NOTIFICATION_WARNING, + LOCKDOWN, + STRONG_BUZZ, +}; + +typedef struct FeedbackGen_Info { + FeedbackGen_Info() + : cmd(FEEDBACK_GEN_CMD::ENABLE) + , effect(FEEDBACK_GEN_EFFECT::NONE) + { + } + FEEDBACK_GEN_CMD cmd; + union { + FEEDBACK_GEN_EFFECT effect; + bool enable; + }; +} FeedbackGen_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_FEEDBACK_GEN_DEF_H \ No newline at end of file diff --git a/x_track/src/App/Service/DataProc/Def/DP_GNSS.h b/x_track/src/App/Service/DataProc/Def/DP_GNSS.h new file mode 100644 index 0000000000000000000000000000000000000000..92f4c0126fb40248296229d52c01ceb80fa71910 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_GNSS.h @@ -0,0 +1,54 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_GNSS_DEF_H +#define __DATA_PROC_GNSS_DEF_H + +#include <cstdint> + +namespace DataProc { + +enum class GNSS_CMD { + NONE, + ENABLE, + DISABLE, + LOAD_EPHEMERIS, + SET_UPDATE_PERIOD, +}; + +typedef struct GNSS_Config { + GNSS_Config() + : cmd(GNSS_CMD::NONE) + , param { 0 } + { + } + + GNSS_CMD cmd; + union { + const char* ephemerisPath; + uint32_t updatePeriod; + } param; +} GNSS_Config_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_GNSS_DEF_H \ No newline at end of file diff --git a/x_track/src/App/Service/DataProc/Def/DP_Global.h b/x_track/src/App/Service/DataProc/Def/DP_Global.h new file mode 100644 index 0000000000000000000000000000000000000000..b254e714a4791c0187470fc3912ce53672856da0 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_Global.h @@ -0,0 +1,52 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_GLOBAL_DEF_H +#define __DATA_PROC_GLOBAL_DEF_H + +#include <cstdint> + +namespace DataProc { + +enum class GLOBAL_EVENT { + NONE, + DATA_PROC_INIT_FINISHED, + PAGE_MANAGER_INIT_FINISHED, + STATUS_BAR_INIT_FINISHED, + APP_ARGS_PARSED, + APP_STARTED, + APP_RUN_LOOP_EXECUTE, + APP_STOPPED, +}; + +typedef struct Global_Info { + Global_Info() + : event(GLOBAL_EVENT::NONE) + { + } + GLOBAL_EVENT event; + void * param; +} Global_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_GLOBAL_DEF_H diff --git a/x_track/src/App/Service/DataProc/Def/DP_LED.h b/x_track/src/App/Service/DataProc/Def/DP_LED.h new file mode 100644 index 0000000000000000000000000000000000000000..dbc970c3192d4203a22944d30ebe84cf6adce683 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_LED.h @@ -0,0 +1,66 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_LED_DEF_H +#define __DATA_PROC_LED_DEF_H + +#include <cstdint> + +namespace DataProc { + +enum class LED_STATUS { + STOP, + RESET, + OFF, + ON, +}; + +enum class LED_ID { + NONE, + RED, + GREEN, + BLUE +}; + +typedef struct LED_Squence { + LED_Squence(LED_STATUS stat, uint16_t dur) + : status(stat) + , duration(dur) + { + } + LED_STATUS status; + uint32_t duration; +} LED_Squence_t; + +typedef struct LED_Info { + LED_Info() + : id(LED_ID::NONE) + , squence(nullptr) + { + } + LED_ID id; + const LED_Squence_t* squence; +} LED_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_LED_DEF_H \ No newline at end of file diff --git a/x_track/src/App/Service/DataProc/Def/DP_MapInfo.h b/x_track/src/App/Service/DataProc/Def/DP_MapInfo.h new file mode 100644 index 0000000000000000000000000000000000000000..3cad5dec813c751c07ba00e072a30ca13cf1033b --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_MapInfo.h @@ -0,0 +1,50 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_MAP_INFO_DEF_H +#define __DATA_PROC_MAP_INFO_DEF_H + +#include <cstdint> + +namespace DataProc { + +typedef struct Map_Info { + Map_Info() + : path(nullptr) + , ext(nullptr) + , tileSize(256) + , levelMin(0) + , levelMax(0) + , coordTrans(false) + { + } + const char* path; + const char* ext; + uint16_t tileSize; + uint8_t levelMin; + uint8_t levelMax; + bool coordTrans; +} Map_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_MAP_INFO_DEF_H \ No newline at end of file diff --git a/x_track/src/App/Service/DataProc/Def/DP_MsgBox.h b/x_track/src/App/Service/DataProc/Def/DP_MsgBox.h new file mode 100644 index 0000000000000000000000000000000000000000..c8dfcbd5446ecef90902abb6cd9a49d23e3d51c7 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_MsgBox.h @@ -0,0 +1,62 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_MSG_BOX_DEF_H +#define __DATA_PROC_MSG_BOX_DEF_H + +#include <cstdint> + +namespace DataProc { + +typedef struct MsgBox_Info { + MsgBox_Info() + : title(nullptr) + , txt(nullptr) + , btns(nullptr) + , defaultBtn(0) + , autoSelectBtn(-1) + , autoSelecTimeoutSec(10) + { + } + union { + struct { + const char* title; + const char* txt; + const char** btns; + int defaultBtn; + int autoSelectBtn; + uint32_t autoSelecTimeoutSec; + }; + + /* return text value */ + + struct + { + int activeBtn; + const char* activeTxt; + }; + }; +} MsgBox_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_MSG_BOX_DEF_H \ No newline at end of file diff --git a/x_track/src/App/Service/DataProc/Def/DP_PageNavi.h b/x_track/src/App/Service/DataProc/Def/DP_PageNavi.h new file mode 100644 index 0000000000000000000000000000000000000000..3fac49f9cb909cae88b88fa1581382651122d162 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_PageNavi.h @@ -0,0 +1,48 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_PAGE_NAVI_DEF_H +#define __DATA_PROC_PAGE_NAVI_DEF_H + +#include <cstdint> + +namespace DataProc { + +enum class PAGE_NAVI_CMD { + NONE, + PUSH, + POP, +}; + +typedef struct PageNavi_Info { + PageNavi_Info() + : cmd(PAGE_NAVI_CMD::NONE) + , name(nullptr) + { + } + PAGE_NAVI_CMD cmd; + const char* name; +} PageNavi_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_PAGE_NAVI_DEF_H diff --git a/x_track/src/App/Service/DataProc/Def/DP_Power.h b/x_track/src/App/Service/DataProc/Def/DP_Power.h new file mode 100644 index 0000000000000000000000000000000000000000..691b9735ee630be6d2d084fd8e536fddd4a1aaaa --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_Power.h @@ -0,0 +1,69 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_POWER_DEF_H +#define __DATA_PROC_POWER_DEF_H + +#include <cstdint> + +namespace DataProc { + +enum class POWER_CMD { + NONE, + SHUTDOWN, + REBOOT, + LOCK_WAKEUP, + UNLOCK_WAKEUP, + KICK_WAKEUP, + SET_AUTO_SHUTDOWN_TIME, + RESET_FUEL_GAUGE, +}; + +typedef struct Power_Info { + Power_Info() + : cmd(POWER_CMD::NONE) + , isReady(false) + , isCharging(false) + , level(0) + , voltage(0) + , delayShutdownTime(2000) + , autoShutdownTime(0) + , batteryUseTime(0) + , uptime(0) + { + } + POWER_CMD cmd; + bool isReady; + bool isCharging; + bool isReadyToShutdown; + bool isBatteryLow; + uint8_t level; + uint16_t voltage; + uint16_t delayShutdownTime; + int autoShutdownTime; + uint64_t batteryUseTime; + uint64_t uptime; +} Power_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_POWER_DEF_H \ No newline at end of file diff --git a/x_track/src/App/Service/DataProc/Def/DP_Recorder.h b/x_track/src/App/Service/DataProc/Def/DP_Recorder.h new file mode 100644 index 0000000000000000000000000000000000000000..db3fd514e664b0dd141f111bef42df70a81d1f92 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_Recorder.h @@ -0,0 +1,51 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_RECORDER_DEF_H +#define __DATA_PROC_RECORDER_DEF_H + +#include <cstdint> + +namespace DataProc { + +enum class RECORDER_CMD { + NONE, + ENABLE_AUTO_REC, + DISABLE_AUTO_REC, +}; + +typedef struct Recorder_Info { + Recorder_Info() + : active(false) + , cmd(RECORDER_CMD::NONE) + , time(0) + { + } + bool active; + bool autoRec; + RECORDER_CMD cmd; + uint16_t time; +} Recorder_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_RECORDER_DEF_H diff --git a/x_track/src/App/Service/DataProc/Def/DP_Slope.h b/x_track/src/App/Service/DataProc/Def/DP_Slope.h new file mode 100644 index 0000000000000000000000000000000000000000..ef4c1134fad21f053545c52a895e96ed2f1c6968 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_Slope.h @@ -0,0 +1,44 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_SLOPE_DEF_H +#define __DATA_PROC_SLOPE_DEF_H + +#include <cstdint> + +namespace DataProc { + +typedef struct Slope_Info { + Slope_Info() + : slope(0) + , angle(0) + , altitude(0) + { + } + float slope; + float angle; + float altitude; +} Slope_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_SLOPE_DEF_H \ No newline at end of file diff --git a/x_track/src/App/Service/DataProc/Def/DP_SportStatus.h b/x_track/src/App/Service/DataProc/Def/DP_SportStatus.h new file mode 100644 index 0000000000000000000000000000000000000000..c846bfa8f6a575ad708cf2c31caf426a5837ffaf --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_SportStatus.h @@ -0,0 +1,80 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_SPORT_STATUS_DEF_H +#define __DATA_PROC_SPORT_STATUS_DEF_H + +#include <cstdint> + +namespace DataProc { + +enum class SPORT_STATUS_CMD { + NONE, + RESET_SINGLE, + RESET_TOTAL, + RESET_SPEED_MAX, + SET_WEIGHT, +}; + +typedef struct SportStatus_Info { + SportStatus_Info() + : cmd(SPORT_STATUS_CMD::NONE) + , lastTick(0) + , weight(0) + , speedKph(0) + , speedMaxKph(0) + , speedAvgKph(0) + , totalTime(0) + , totalDistance(0) + , singleTime(0) + , singleDistance(0) + , singleCalorie(0) + , longitude(0) + , latitude(0) + , altitude(0) + { + } + + SPORT_STATUS_CMD cmd; + + uint32_t lastTick; + + float weight; + + float speedKph; + float speedMaxKph; + float speedAvgKph; + + uint64_t totalTime; + float totalDistance; + uint64_t singleTime; + float singleDistance; + float singleCalorie; + + float longitude; + float latitude; + float altitude; +} SportStatus_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_SPORT_STATUS_DEF_H diff --git a/x_track/src/App/Service/DataProc/Def/DP_Storage.h b/x_track/src/App/Service/DataProc/Def/DP_Storage.h new file mode 100644 index 0000000000000000000000000000000000000000..b50b65707400c9f9537a7d1963e739c5efdc6ba8 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_Storage.h @@ -0,0 +1,80 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_STORAGE_DEF_H +#define __DATA_PROC_STORAGE_DEF_H + +#include <cstdint> + +namespace DataProc { + +enum class STORAGE_CMD { + LOAD, + SAVE, + ADD, + MOUNT, + UNMOUNT, +}; + +enum class STORAGE_TYPE { + BOOL = 1, + INT, + FLOAT, + DOUBLE, + STRING, + ARRAY, + STRUCT_START, + STRUCT_END, +}; + +typedef struct Storage_Info { + Storage_Info() + : cmd(STORAGE_CMD::LOAD) + , key(nullptr) + , value(nullptr) + , size(0) + , type(STORAGE_TYPE::BOOL) + { + } + STORAGE_CMD cmd; + const char* key; + void* value; + uint16_t size; + STORAGE_TYPE type; +} Storage_Info_t; + +typedef struct Storage_Device_Info { + Storage_Device_Info() + : isDetect(false) + , totalSize(0) + , freeSize(0) + { + } + bool isDetect; + bool isActive; + uint64_t totalSize; + uint64_t freeSize; +} Storage_Device_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_STORAGE_DEF_H diff --git a/x_track/src/App/Service/DataProc/Def/DP_SunRise.h b/x_track/src/App/Service/DataProc/Def/DP_SunRise.h new file mode 100644 index 0000000000000000000000000000000000000000..a5e844a3d226829738c67eb8df993940c1d32e4f --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_SunRise.h @@ -0,0 +1,50 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_SUN_RISE_DEF_H +#define __DATA_PROC_SUN_RISE_DEF_H + +#include <cstdint> + +namespace DataProc { + +enum class SUNRISE_STATE { + UNKNOWN, + DAY, + NIGHT, +}; + +typedef struct SunRise_Info { + SunRise_Info() + : state(SUNRISE_STATE::UNKNOWN) + { + } + SUNRISE_STATE state; + uint8_t sunriseHour; + uint8_t sunriseMinute; + uint8_t sunsetHour; + uint8_t sunsetMinute; +} SunRise_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_SUN_RISE_DEF_H \ No newline at end of file diff --git a/x_track/src/App/Service/DataProc/Def/DP_Toast.h b/x_track/src/App/Service/DataProc/Def/DP_Toast.h new file mode 100644 index 0000000000000000000000000000000000000000..0f4a9b720c7bc3ba70fa6127939ef556f19c32e3 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_Toast.h @@ -0,0 +1,42 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_TOAST_DEF_H +#define __DATA_PROC_TOAST_DEF_H + +#include <cstdint> + +namespace DataProc { + +typedef struct Toast_Info { + Toast_Info() + : txt(nullptr) + , duration(0) + { + } + const char* txt; + uint32_t duration; +} Toast_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_TOAST_DEF_H \ No newline at end of file diff --git a/x_track/src/App/Service/DataProc/Def/DP_TrackFilter.h b/x_track/src/App/Service/DataProc/Def/DP_TrackFilter.h new file mode 100644 index 0000000000000000000000000000000000000000..0b1fd5a1f168132b0d79bc89fee2eb619916d31f --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_TrackFilter.h @@ -0,0 +1,44 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_TRACK_FILTER_DEF_H +#define __DATA_PROC_TRACK_FILTER_DEF_H + +#include <cstdint> + +namespace DataProc { + +typedef struct TrackFilter_Info { + TrackFilter_Info() + : active(false) + , level(0) + , pointCont(nullptr) + { + } + bool active; + uint8_t level; + void* pointCont; +} TrackFilter_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_TRACK_FILTER_DEF_H \ No newline at end of file diff --git a/x_track/src/App/Service/DataProc/Def/DP_Version.h b/x_track/src/App/Service/DataProc/Def/DP_Version.h new file mode 100644 index 0000000000000000000000000000000000000000..d2182c9b42f1d7d018e1faf124d5a02d8589a5b8 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/DP_Version.h @@ -0,0 +1,56 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_VERSION_DEF_H +#define __DATA_PROC_VERSION_DEF_H + +#include <cstdint> + +namespace DataProc { + +typedef struct Version_Info { + Version_Info() + : name(nullptr) + , software(nullptr) + , hardware(nullptr) + , author(nullptr) + , website(nullptr) + , graphics(nullptr) + , compiler(nullptr) + , buildDate(nullptr) + , buildTime(nullptr) + { + } + const char* name; + const char* software; + const char* hardware; + const char* author; + const char* website; + const char* graphics; + const char* compiler; + const char* buildDate; + const char* buildTime; +} Version_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_VERSION_DEF_H \ No newline at end of file diff --git a/x_track/src/App/Service/DataProc/Def/_DP_Template.h b/x_track/src/App/Service/DataProc/Def/_DP_Template.h new file mode 100644 index 0000000000000000000000000000000000000000..7e010ce8ed07a0855076ae200e6af9c2a9d9652f --- /dev/null +++ b/x_track/src/App/Service/DataProc/Def/_DP_Template.h @@ -0,0 +1,45 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_TEMPLATE_DEF_H +#define __DATA_PROC_TEMPLATE_DEF_H + +#include <cstdint> + +namespace DataProc { + +enum class TEMPLATE_CMD { + NONE, +}; + +typedef struct Template_Info { + Template_Info() + : cmd(TEMPLATE_CMD::NONE) + { + } + TEMPLATE_CMD cmd; + int value; +} Template_Info_t; + +} // namespace DataProc + +#endif // __DATA_PROC_TEMPLATE_DEF_H diff --git a/x_track/src/App/Service/DataProc/Helper/Env_Helper.cpp b/x_track/src/App/Service/DataProc/Helper/Env_Helper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..26f9af36001970c658a36ee5fd564a657a366847 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Helper/Env_Helper.cpp @@ -0,0 +1,62 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Env_Helper.h" +#include "Frameworks/DataBroker/DataBroker.h" +#include <cstdio> +#include <cstdlib> + +using namespace DataProc; + +Env_Helper::Env_Helper(DataNode* node) + : _node(node) +{ + _nodeEnv = node->subscribe("Env"); +} + +int Env_Helper::set(const char* key, const char* value) +{ + DataProc::Env_Info_t envInfo; + envInfo.key = key; + envInfo.value = value; + return _node->notify(_nodeEnv, &envInfo, sizeof(envInfo)); +} + +int Env_Helper::setInt(const char* key, int value) +{ + char buf[16]; + snprintf(buf, sizeof(buf), "%d", value); + return set(key, buf); +} + +const char* Env_Helper::get(const char* key, const char* def) +{ + DataProc::Env_Info_t envInfo; + envInfo.key = key; + _node->pull(_nodeEnv, &envInfo, sizeof(envInfo)); + return envInfo.value ? envInfo.value : def; +} + +int Env_Helper::getInt(const char* key) +{ + return atoi(get(key, "0")); +} diff --git a/x_track/src/App/Service/DataProc/Helper/Env_Helper.h b/x_track/src/App/Service/DataProc/Helper/Env_Helper.h new file mode 100644 index 0000000000000000000000000000000000000000..b6f505ce57b7f794f3d26d6335c21395bb8f9c70 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Helper/Env_Helper.h @@ -0,0 +1,51 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_ENV_HELPER_H +#define __DATA_PROC_ENV_HELPER_H + +#include "../Def/DP_Env.h" + +class DataNode; + +namespace DataProc { + +class Env_Helper { +public: + Env_Helper(DataNode* node); + int set(const char* key, const char* value); + int setInt(const char* key, int value); + const char* get(const char* key, const char* def = nullptr); + int getInt(const char* key); + operator const DataNode*() const + { + return _nodeEnv; + } + +private: + DataNode* _node; + const DataNode* _nodeEnv; +}; + +} // namespace DataProc + +#endif // __DATA_PROC_ENV_HELPER_H diff --git a/x_track/src/App/Service/DataProc/Helper/FeedbackGen_Helper.cpp b/x_track/src/App/Service/DataProc/Helper/FeedbackGen_Helper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..083016482468bc63c72a1e7bfde1510eff089991 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Helper/FeedbackGen_Helper.cpp @@ -0,0 +1,47 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "FeedbackGen_Helper.h" +#include "Frameworks/DataBroker/DataBroker.h" + +using namespace DataProc; + +FeedbackGen_Helper::FeedbackGen_Helper(DataNode* node) + : _node(node) +{ + _nodeFeedBackGen = node->subscribe("FeedbackGen"); +} + +int FeedbackGen_Helper::trigger(DataProc::FEEDBACK_GEN_EFFECT effect) +{ + DataProc::FeedbackGen_Info_t info; + info.cmd = DataProc::FEEDBACK_GEN_CMD::PREPARE; + info.effect = effect; + + int retval = _node->notify(_nodeFeedBackGen, &info, sizeof(info)); + if (retval != DataNode::RES_OK) { + return retval; + } + + info.cmd = DataProc::FEEDBACK_GEN_CMD::TRIGGER; + return _node->notify(_nodeFeedBackGen, &info, sizeof(info)); +} diff --git a/x_track/src/App/Service/DataProc/Helper/FeedbackGen_Helper.h b/x_track/src/App/Service/DataProc/Helper/FeedbackGen_Helper.h new file mode 100644 index 0000000000000000000000000000000000000000..0c70455f7f0d825373f34524c18c8f2dd1515843 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Helper/FeedbackGen_Helper.h @@ -0,0 +1,48 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_FEEDBACK_GEN_HELPER_H +#define __DATA_PROC_FEEDBACK_GEN_HELPER_H + +#include "../Def/DP_FeedbackGen.h" + +class DataNode; + +namespace DataProc { + +class FeedbackGen_Helper { +public: + FeedbackGen_Helper(DataNode* node); + int trigger(DataProc::FEEDBACK_GEN_EFFECT effect); + operator const DataNode*() const + { + return _nodeFeedBackGen; + } + +private: + DataNode* _node; + const DataNode* _nodeFeedBackGen; +}; + +} // namespace DataProc + +#endif // __DATA_PROC_FEEDBACK_GEN_HELPER_H diff --git a/x_track/src/App/Service/DataProc/Helper/Global_Helper.cpp b/x_track/src/App/Service/DataProc/Helper/Global_Helper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9224b32e91dfd3d4e9683cc88f1eefbe9f42e28c --- /dev/null +++ b/x_track/src/App/Service/DataProc/Helper/Global_Helper.cpp @@ -0,0 +1,40 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Global_Helper.h" +#include "Frameworks/DataBroker/DataBroker.h" + +using namespace DataProc; + +Global_Helper::Global_Helper(DataNode* node) +{ + _node = node; + _nodeGlobal = node->subscribe("Global"); +} + +int Global_Helper::publish(GLOBAL_EVENT event, void* param) +{ + Global_Info_t info; + info.event = event; + info.param = param; + return _node->notify(_nodeGlobal, &info, sizeof(info)); +} diff --git a/x_track/src/App/Service/DataProc/Helper/Global_Helper.h b/x_track/src/App/Service/DataProc/Helper/Global_Helper.h new file mode 100644 index 0000000000000000000000000000000000000000..8cbace0b64391f0f04494af98fa290c0b537d9e7 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Helper/Global_Helper.h @@ -0,0 +1,44 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_GLOBAL_HELPER_H +#define __DATA_PROC_GLOBAL_HELPER_H + +#include "../Def/DP_Global.h" + +class DataNode; + +namespace DataProc { + +class Global_Helper { +public: + Global_Helper(DataNode* node); + int publish(GLOBAL_EVENT event, void* param = nullptr); + +private: + DataNode* _node; + const DataNode* _nodeGlobal; +}; + +} // namespace DataProc + +#endif // __DATA_PROC_GLOBAL_HELPER_H diff --git a/x_track/src/App/Service/DataProc/Helper/LED_Helper.cpp b/x_track/src/App/Service/DataProc/Helper/LED_Helper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..df0c56c255def79045a71ebe5b2a7aad83e88606 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Helper/LED_Helper.cpp @@ -0,0 +1,61 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "LED_Helper.h" +#include "Frameworks/DataBroker/DataBroker.h" + +using namespace DataProc; + +LED_Helper::LED_Helper(DataNode* node, LED_ID id) + : _node(node) + , _nodeLED(node->subscribe("LED")) + , _id(id) +{ +} + +int LED_Helper::start(const LED_Squence_t* squence) +{ + LED_Info_t info; + info.id = _id; + info.squence = squence; + return _node->notify(_nodeLED, &info, sizeof(info)); +} + +int LED_Helper::on() +{ + static const LED_Squence_t seq[] = { + { LED_STATUS::ON, 0 }, + { LED_STATUS::STOP, 0 }, + }; + + return start(seq); +} + +int LED_Helper::off() +{ + static const LED_Squence_t seq[] = { + { LED_STATUS::OFF, 0 }, + { LED_STATUS::STOP, 0 }, + }; + + return start(seq); +} diff --git a/x_track/src/App/Service/DataProc/Helper/LED_Helper.h b/x_track/src/App/Service/DataProc/Helper/LED_Helper.h new file mode 100644 index 0000000000000000000000000000000000000000..982056e0829ff79ac0190840494aa0ebe40ffd4e --- /dev/null +++ b/x_track/src/App/Service/DataProc/Helper/LED_Helper.h @@ -0,0 +1,51 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_LED_HELPER_H +#define __DATA_PROC_LED_HELPER_H + +#include "../Def/DP_LED.h" + +class DataNode; + +namespace DataProc { + +class LED_Helper { +public: + LED_Helper(DataNode* node, LED_ID id); + int start(const LED_Squence_t* squence); + int on(); + int off(); + operator const DataNode*() const + { + return _nodeLED; + } + +private: + DataNode* _node; + const DataNode* _nodeLED; + LED_ID _id; +}; + +} // namespace DataProc + +#endif // __DATA_PROC_LED_HELPER_H diff --git a/x_track/src/App/Service/DataProc/Helper/MsgBox_Helper.cpp b/x_track/src/App/Service/DataProc/Helper/MsgBox_Helper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9ac0ff78354476d23553d5789f987a8cddfd0daf --- /dev/null +++ b/x_track/src/App/Service/DataProc/Helper/MsgBox_Helper.cpp @@ -0,0 +1,46 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "MsgBox_Helper.h" +#include "Frameworks/DataBroker/DataBroker.h" + +using namespace DataProc; + +MsgBox_Helper::MsgBox_Helper(DataNode* node) + : _node(node) +{ + _nodeMsgBox = node->subscribe("MsgBox"); +} + +int MsgBox_Helper::show(const char* title, const char* txt, const char** btns) +{ + DataProc::MsgBox_Info_t info; + info.title = title; + info.txt = txt; + info.btns = btns; + return show(&info); +} + +int MsgBox_Helper::show(const DataProc::MsgBox_Info_t* info) +{ + return _node->notify(_nodeMsgBox, info, sizeof(DataProc::MsgBox_Info_t)); +} diff --git a/x_track/src/App/Service/DataProc/Helper/MsgBox_Helper.h b/x_track/src/App/Service/DataProc/Helper/MsgBox_Helper.h new file mode 100644 index 0000000000000000000000000000000000000000..05f0071faa482d1e12060663c1d18fe02bc3cc79 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Helper/MsgBox_Helper.h @@ -0,0 +1,49 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_MSGBOX_HELPER_H +#define __DATA_PROC_MSGBOX_HELPER_H + +#include "../Def/DP_MsgBox.h" + +class DataNode; + +namespace DataProc { + +class MsgBox_Helper { +public: + MsgBox_Helper(DataNode* node); + int show(const char* title, const char* txt, const char** btns); + int show(const DataProc::MsgBox_Info_t* info); + operator const DataNode*() const + { + return _nodeMsgBox; + } + +private: + DataNode* _node; + const DataNode* _nodeMsgBox; +}; + +} // namespace DataProc + +#endif // __DATA_PROC_MSGBOX_HELPER_H diff --git a/x_track/src/App/Service/DataProc/Helper/Storage_Helper.cpp b/x_track/src/App/Service/DataProc/Helper/Storage_Helper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..037dc45e1a0f49dd972721189016ab10f40c9f43 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Helper/Storage_Helper.cpp @@ -0,0 +1,101 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Storage_Helper.h" +#include "Frameworks/DataBroker/DataBroker.h" + +using namespace DataProc; + +Storage_Helper::Storage_Helper(DataNode* node) + : _node(node) +{ + _nodeStorage = node->subscribe("Storage"); +} + +int Storage_Helper::add(const char* key, void* value, uint32_t size, DataProc::STORAGE_TYPE type) +{ + DataProc::Storage_Info_t storageInfo; + storageInfo.cmd = DataProc::STORAGE_CMD::ADD; + storageInfo.key = key; + storageInfo.value = value; + storageInfo.size = size; + storageInfo.type = type; + return _node->notify(_nodeStorage, &storageInfo, sizeof(storageInfo)); +} + +int Storage_Helper::add(const char* key, float* value) +{ + return add(key, value, sizeof(float), DataProc::STORAGE_TYPE::FLOAT); +} + +int Storage_Helper::add(const char* key, int* value) +{ + return add(key, value, sizeof(int), DataProc::STORAGE_TYPE::INT); +} + +int Storage_Helper::add(const char* key, bool* value) +{ + return add(key, value, sizeof(bool), DataProc::STORAGE_TYPE::BOOL); +} + +int Storage_Helper::addArray(const char* key, void* value, uint32_t size, uint32_t len, DataProc::STORAGE_TYPE type) +{ + DataProc::Storage_Info_t storageInfo; + storageInfo.cmd = DataProc::STORAGE_CMD::ADD; + storageInfo.key = key; + storageInfo.size = len; + storageInfo.type = DataProc::STORAGE_TYPE::ARRAY; + + auto ret = _node->notify(_nodeStorage, &storageInfo, sizeof(storageInfo)); + if (ret != DataNode::RES_OK) { + return ret; + } + + storageInfo.cmd = DataProc::STORAGE_CMD::ADD; + storageInfo.key = nullptr; + storageInfo.value = value; + storageInfo.size = size; + storageInfo.type = type; + return _node->notify(_nodeStorage, &storageInfo, sizeof(storageInfo)); +} + +int Storage_Helper::structStart(const char* key) +{ + DataProc::Storage_Info_t storageInfo; + storageInfo.cmd = DataProc::STORAGE_CMD::ADD; + storageInfo.key = key; + storageInfo.value = nullptr; + storageInfo.size = 0; + storageInfo.type = DataProc::STORAGE_TYPE::STRUCT_START; + return _node->notify(_nodeStorage, &storageInfo, sizeof(storageInfo)); +} + +int Storage_Helper::structEnd() +{ + DataProc::Storage_Info_t storageInfo; + storageInfo.cmd = DataProc::STORAGE_CMD::ADD; + storageInfo.key = nullptr; + storageInfo.value = nullptr; + storageInfo.size = 0; + storageInfo.type = DataProc::STORAGE_TYPE::STRUCT_END; + return _node->notify(_nodeStorage, &storageInfo, sizeof(storageInfo)); +} diff --git a/x_track/src/App/Service/DataProc/Helper/Storage_Helper.h b/x_track/src/App/Service/DataProc/Helper/Storage_Helper.h new file mode 100644 index 0000000000000000000000000000000000000000..51f0e19bd18f58b15176de4da004b066128d6047 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Helper/Storage_Helper.h @@ -0,0 +1,54 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_STORAGE_HELPER_H +#define __DATA_PROC_STORAGE_HELPER_H + +#include "../Def/DP_Storage.h" + +class DataNode; + +namespace DataProc { + +class Storage_Helper { +public: + Storage_Helper(DataNode* node); + int add(const char* key, void* value, uint32_t size, DataProc::STORAGE_TYPE type); + int add(const char* key, float* value); + int add(const char* key, int* value); + int add(const char* key, bool* value); + int addArray(const char* key, void* value, uint32_t size, uint32_t len, DataProc::STORAGE_TYPE type); + int structStart(const char* key); + int structEnd(); + operator const DataNode*() const + { + return _nodeStorage; + } + +private: + DataNode* _node; + const DataNode* _nodeStorage; +}; + +} // namespace DataProc + +#endif // __DATA_PROC_STORAGE_HELPER_H diff --git a/x_track/src/App/Service/DataProc/Helper/Toast_Helper.cpp b/x_track/src/App/Service/DataProc/Helper/Toast_Helper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0b21cdc2a04bcdc56a4f5c0529e737b7daa1260c --- /dev/null +++ b/x_track/src/App/Service/DataProc/Helper/Toast_Helper.cpp @@ -0,0 +1,50 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Toast_Helper.h" +#include "Frameworks/DataBroker/DataBroker.h" +#include <cstdarg> +#include <cstdio> + +using namespace DataProc; + +Toast_Helper::Toast_Helper(DataNode* node) +{ + _node = node; + _nodeToast = node->subscribe("Toast"); +} + +int Toast_Helper::show(const char* format, ...) +{ + va_list args; + va_start(args, format); + + char buf[256]; + vsnprintf(buf, sizeof(buf), format, args); + + va_end(args); + + Toast_Info_t info; + info.txt = buf; + info.duration = 2000; + return _node->notify(_nodeToast, &info, sizeof(info)); +} diff --git a/x_track/src/App/Service/DataProc/Helper/Toast_Helper.h b/x_track/src/App/Service/DataProc/Helper/Toast_Helper.h new file mode 100644 index 0000000000000000000000000000000000000000..19a715cae902c36be890c82e3203e54fdc6c4e14 --- /dev/null +++ b/x_track/src/App/Service/DataProc/Helper/Toast_Helper.h @@ -0,0 +1,48 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_TOAST_HELPER_H +#define __DATA_PROC_TOAST_HELPER_H + +#include "../Def/DP_Toast.h" + +class DataNode; + +namespace DataProc { + +class Toast_Helper { +public: + Toast_Helper(DataNode* node); + int show(const char* format, ...); + operator const DataNode*() const + { + return _nodeToast; + } + +private: + DataNode* _node; + const DataNode* _nodeToast; +}; + +} // namespace DataProc + +#endif // __DATA_PROC_TOAST_HELPER_H diff --git a/x_track/src/App/Service/DataProc/Helper/_Template_Helper.cpp b/x_track/src/App/Service/DataProc/Helper/_Template_Helper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c7127aa98bba51403a0b5e442d8ff60552aac54c --- /dev/null +++ b/x_track/src/App/Service/DataProc/Helper/_Template_Helper.cpp @@ -0,0 +1,32 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "_Template_Helper.h" +#include "Frameworks/DataBroker/DataBroker.h" + +using namespace DataProc; + +Template_Helper::Template_Helper(DataNode* node) + : _node(node) +{ + _nodeTemplate = node->subscribe("Template"); +} diff --git a/x_track/src/App/Service/DataProc/Helper/_Template_Helper.h b/x_track/src/App/Service/DataProc/Helper/_Template_Helper.h new file mode 100644 index 0000000000000000000000000000000000000000..c0ec4897315d37422bcd7b2d0c7e99d7874b936a --- /dev/null +++ b/x_track/src/App/Service/DataProc/Helper/_Template_Helper.h @@ -0,0 +1,45 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_PROC_TEMPLATE_HELPER_H +#define __DATA_PROC_TEMPLATE_HELPER_H + +class DataNode; + +namespace DataProc { + +class Template_Helper { +public: + Template_Helper(DataNode* node); + operator const DataNode*() const + { + return _nodeTemplate; + } + +private: + DataNode* _node; + const DataNode* _nodeTemplate; +}; + +} // namespace DataProc + +#endif // __DATA_PROC_TEMPLATE_HELPER_H diff --git a/x_track/src/App/Service/DataProc/_DP_Template.cpp b/x_track/src/App/Service/DataProc/_DP_Template.cpp new file mode 100644 index 0000000000000000000000000000000000000000..81e028e78497062d0ed342d685ff6eece85fb438 --- /dev/null +++ b/x_track/src/App/Service/DataProc/_DP_Template.cpp @@ -0,0 +1,73 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "DataProc.h" + +using namespace DataProc; + +class DP_Template { +public: + DP_Template(DataNode* node); + +private: + DataNode* _node; + int _value; + +private: + int onEvent(DataNode::EventParam_t* param); +}; + +DP_Template::DP_Template(DataNode* node) + : _node(node) + , _value(0) +{ + _node->setEventCallback( + [](DataNode* n, DataNode::EventParam_t* param) { + auto ctx = (DP_Template*)n->getUserData(); + return ctx->onEvent(param); + }, + DataNode::EVENT_PULL | DataNode::EVENT_NOTIFY); +} + +int DP_Template::onEvent(DataNode::EventParam_t* param) +{ + if (param->size != sizeof(_value)) { + return DataNode::RES_SIZE_MISMATCH; + } + + switch (param->event) { + case DataNode::EVENT_PULL: + memcpy(param->data_p, &_value, sizeof(_value)); + break; + + case DataNode::EVENT_NOTIFY: + memcpy(&_value, param->data_p, sizeof(_value)); + break; + + default: + break; + } + + return DataNode::RES_OK; +} + +DATA_PROC_DESCRIPTOR_DEF(Template) diff --git a/x_track/src/App/Service/HAL/Def/HAL_Backlight.h b/x_track/src/App/Service/HAL/Def/HAL_Backlight.h new file mode 100644 index 0000000000000000000000000000000000000000..e8fcf1bdc883ffe30c37fd2a372b12e5ba83e5ed --- /dev/null +++ b/x_track/src/App/Service/HAL/Def/HAL_Backlight.h @@ -0,0 +1,45 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __HAL_BACKLIGHT_DEF_H +#define __HAL_BACKLIGHT_DEF_H + +#include <stdbool.h> +#include <stdint.h> + +namespace HAL { + +/* clang-format off */ + +/* DEVICEO_OBJECT_IOCMD_DEF(dir, size, type, number) */ + +/********************* + * Backlight + *********************/ + +#define BACKLIGHT_IOCMD_FORCE_LIT DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, 0, 0, 0) + +/* clang-format on */ + +} // namespace HAL + +#endif // __HAL_BACKLIGHT_DEF_H diff --git a/x_track/src/App/Service/HAL/Def/HAL_Clock.h b/x_track/src/App/Service/HAL/Def/HAL_Clock.h new file mode 100644 index 0000000000000000000000000000000000000000..d647369a1018f5ed8e91cf8c92ed13c72013bf80 --- /dev/null +++ b/x_track/src/App/Service/HAL/Def/HAL_Clock.h @@ -0,0 +1,57 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __HAL_CLOCK_DEF_H +#define __HAL_CLOCK_DEF_H + +#include <stdbool.h> +#include <stdint.h> + +namespace HAL { + +/* clang-format off */ + +/* DEVICEO_OBJECT_IOCMD_DEF(dir, size, type, number) */ + +/********************* + * Clock + *********************/ + +typedef struct +{ + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t week; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint16_t millisecond; +} Clock_Info_t; + +#define CLOCK_IOCMD_CALIBRATE DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, sizeof(HAL::Clock_Info_t), 0, 0) + +/* clang-format on */ + +} // namespace HAL + +#endif // __HAL_CLOCK_DEF_H diff --git a/x_track/src/App/Service/HAL/Def/HAL_Display.h b/x_track/src/App/Service/HAL/Def/HAL_Display.h new file mode 100644 index 0000000000000000000000000000000000000000..0f220bca3ed54e522a423fd41e0f1a4c48424a36 --- /dev/null +++ b/x_track/src/App/Service/HAL/Def/HAL_Display.h @@ -0,0 +1,86 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __HAL_DISPLAY_DEF_H +#define __HAL_DISPLAY_DEF_H + +#include <stdbool.h> +#include <stdint.h> +#include <stddef.h> + +namespace HAL { + +/* clang-format off */ + +/* DEVICEO_OBJECT_IOCMD_DEF(dir, size, type, number) */ + +/********************* + * Display + *********************/ + +typedef struct +{ + int16_t width; + int16_t height; + void* buffer0; + void* buffer1; + size_t size; + uint8_t bpp; +} Display_Info_t; + +typedef struct +{ + int16_t x0; + int16_t y0; + int16_t x1; + int16_t y1; + void* buffer; + size_t size; +} Display_PutArea_Info_t; + +typedef void (*Display_CallbackFunc_t)(void*); + +typedef struct +{ + Display_CallbackFunc_t callback; + void* userData; +} Display_SetCallback_Info_t; + +typedef struct +{ + const char* title; + const char* info; + const char* callstack; +} Display_ShowCrash_Info_t; + +#define DISPLAY_IOCMD_GET_INFO DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_OUT, sizeof(HAL::Display_Info_t), 0, 0) +#define DISPLAY_IOCMD_PUT_AREA DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, sizeof(HAL::Display_PutArea_Info_t), 1, 0) +#define DISPLAY_IOCMD_SET_CALLBACK DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, sizeof(HAL::Display_SetCallback_Info_t), 2, 0) +#define DISPLAY_IOCMD_SET_ROTATION DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, sizeof(int), 3, 0) +#define DISPLAY_IOCMD_SHOW_CRASH_INFO DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, sizeof(HAL::Display_ShowCrash_Info_t), 4, 0) +#define DISPLAY_IOCMD_FILL_COLOR DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, sizeof(uint16_t), 5, 0) + +/* clang-format on */ + +} // namespace HAL + +#endif // __HAL_DISPLAY_DEF_H diff --git a/x_track/src/App/Service/HAL/Def/HAL_GNSS.h b/x_track/src/App/Service/HAL/Def/HAL_GNSS.h new file mode 100644 index 0000000000000000000000000000000000000000..f4e4a55f01d86bea2c5d8f038932549e196dcb83 --- /dev/null +++ b/x_track/src/App/Service/HAL/Def/HAL_GNSS.h @@ -0,0 +1,77 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __HAL_GNSS_DEF_H +#define __HAL_GNSS_DEF_H + +#include <stdbool.h> +#include <stdint.h> + +namespace HAL { + +/* clang-format off */ + +/* DEVICEO_OBJECT_IOCMD_DEF(dir, size, type, number) */ + +/********************* + * GNSS + *********************/ + +typedef struct +{ + uint8_t id; + int16_t elevation; + int16_t azimuth; + int16_t snr; +} GNSS_GSV_Item_t; + +typedef struct +{ + bool isVaild; + int16_t satellites; + Clock_Info_t clock; + + float longitude; + float latitude; + float altitude; + float course; + float speed; + + float pdop; + float hdop; + float vdop; + + struct + { + const GNSS_GSV_Item_t* items; + uint8_t num; + int16_t satsInView; + } gsv; +} GNSS_Info_t; + +#define GNSS_IOCMD_UPDATE DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, 0, 0, 0) + +/* clang-format on */ + +} // namespace HAL + +#endif // __HAL_GNSS_DEF_H diff --git a/x_track/src/App/Service/HAL/Def/HAL_Power.h b/x_track/src/App/Service/HAL/Def/HAL_Power.h new file mode 100644 index 0000000000000000000000000000000000000000..e66297d2ef2845c44e47f42d01e2378925178a12 --- /dev/null +++ b/x_track/src/App/Service/HAL/Def/HAL_Power.h @@ -0,0 +1,57 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __HAL_POWER_DEF_H +#define __HAL_POWER_DEF_H + +#include <stdbool.h> +#include <stdint.h> + +namespace HAL { + +/* clang-format off */ + +/* DEVICEO_OBJECT_IOCMD_DEF(dir, size, type, number) */ + +/********************* + * Power + *********************/ + +typedef struct +{ + uint16_t voltage; + uint8_t level; + bool isReady; + bool isCharging; +} Power_Info_t; + +#define POWER_IOCMD_POWER_ON DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, 0, 0, 0) +#define POWER_IOCMD_POWER_OFF DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, 0, 1, 0) +#define POWER_IOCMD_REBOOT DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, 0, 2, 0) +#define POWER_IOCMD_GAUGE_RESET DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, 0, 3, 0) +#define POWER_IOCMD_SET_SYS_VOLTAGE DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, sizeof(int), 5, 0) + +/* clang-format on */ + +} // namespace HAL + +#endif // __HAL_TEMPLATE_DEF_H diff --git a/x_track/src/App/Service/HAL/Def/HAL_SdCard.h b/x_track/src/App/Service/HAL/Def/HAL_SdCard.h new file mode 100644 index 0000000000000000000000000000000000000000..c9a7e8a0dd2623edc3b2d95176210c7ac5923847 --- /dev/null +++ b/x_track/src/App/Service/HAL/Def/HAL_SdCard.h @@ -0,0 +1,56 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __HAL_SDCARD_DEF_H +#define __HAL_SDCARD_DEF_H + +#include <stdbool.h> +#include <stdint.h> + +namespace HAL { + +/* clang-format off */ + +/* DEVICEO_OBJECT_IOCMD_DEF(dir, size, type, number) */ + +/********************* + * SD-Card + *********************/ +typedef struct +{ + bool isInsert; + bool isMounted; + bool isActive; + uint64_t freeCapacity; + uint64_t totalCapacity; +} SdCard_Info_t; + +#define SDCARD_IOCMD_MOUNT DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, 0, 1, 0) +#define SDCARD_IOCMD_UNMOUNT DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, 0, 2, 0) +#define SDCARD_IOCMD_MKDIR DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, sizeof(const char*), 3, 0) +#define SDCARD_IOCMD_REMOVE DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, sizeof(const char*), 4, 0) + +/* clang-format on */ + +} // namespace HAL + +#endif // __HAL_SDCARD_DEF_H diff --git a/x_track/src/App/Service/HAL/Def/_HAL_Template.h b/x_track/src/App/Service/HAL/Def/_HAL_Template.h new file mode 100644 index 0000000000000000000000000000000000000000..be7ad8cca814dd6ca2f678d6c1f2286389178ce5 --- /dev/null +++ b/x_track/src/App/Service/HAL/Def/_HAL_Template.h @@ -0,0 +1,51 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __HAL_TEMPLATE_DEF_H +#define __HAL_TEMPLATE_DEF_H + +#include <stdbool.h> +#include <stdint.h> + +namespace HAL { + +/* clang-format off */ + +/* DEVICEO_OBJECT_IOCMD_DEF(dir, size, type, number) */ + +/********************* + * Template + *********************/ + +typedef struct +{ + int value; +} Template_Info_t; + +#define TEMPLATE_IOCMD_ON DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, 0, 0, 0) +#define TEMPLATE_IOCMD_OFF DEVICE_OBJECT_IOCMD_DEF(DeviceObject::DIR_IN, 0, 1, 0) + +/* clang-format on */ + +} // namespace HAL + +#endif // __HAL_TEMPLATE_DEF_H diff --git a/x_track/src/App/Service/HAL/HAL.h b/x_track/src/App/Service/HAL/HAL.h new file mode 100644 index 0000000000000000000000000000000000000000..989abda35fe34090ad6592c48a642a55e9869512 --- /dev/null +++ b/x_track/src/App/Service/HAL/HAL.h @@ -0,0 +1,41 @@ +/* + * MIT License + * Copyright (c) 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __HAL_COMMON_H +#define __HAL_COMMON_H + +#include "HAL_Def.h" + +#include "Frameworks/DeviceManager/DeviceManager.h" +#include "Frameworks/DeviceManager/DeviceObject.h" + +#define DEVICE_OBJECT_MAKE(name) HAL::name DevObj_##name(#name) + +namespace HAL { + +void Init(); +DeviceManager* Manager(); +uint32_t GetTick(); +uint32_t GetTickElaps(uint32_t prevTick); + +} +#endif diff --git a/x_track/src/App/Service/HAL/HAL_Assert.h b/x_track/src/App/Service/HAL/HAL_Assert.h new file mode 100644 index 0000000000000000000000000000000000000000..4f42b92f8fb158d071a6f5ddcac0ffccef052638 --- /dev/null +++ b/x_track/src/App/Service/HAL/HAL_Assert.h @@ -0,0 +1,44 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __HAL_PANIC_H +#define __HAL_PANIC_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define HAL_ASSERT(expr) \ + do { \ + if (!(expr)) \ + HAL_Assert(__FILE__, __LINE__, __func__, #expr); \ + } while (0) + +void HAL_Assert(const char* file, int line, const char* func, const char* expr); +void HAL_Panic(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __HAL_PANIC_H */ diff --git a/x_track/src/App/Service/HAL/HAL_Def.h b/x_track/src/App/Service/HAL/HAL_Def.h new file mode 100644 index 0000000000000000000000000000000000000000..eddccce81fec82e4c66d0d9f5cb463973ddb4532 --- /dev/null +++ b/x_track/src/App/Service/HAL/HAL_Def.h @@ -0,0 +1,34 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __HAL_DEF_H +#define __HAL_DEF_H + +#include "Def/HAL_Backlight.h" +#include "Def/HAL_Clock.h" +#include "Def/HAL_Display.h" +#include "Def/HAL_GNSS.h" +#include "Def/HAL_Power.h" +#include "Def/HAL_SdCard.h" + +#endif /* __HAL_DEF_H */ diff --git a/x_track/src/App/Service/HAL/HAL_Log.h b/x_track/src/App/Service/HAL/HAL_Log.h new file mode 100644 index 0000000000000000000000000000000000000000..e4f1c7409bc79a15c4405b4043abff323b6d611e --- /dev/null +++ b/x_track/src/App/Service/HAL/HAL_Log.h @@ -0,0 +1,79 @@ +/* + * MIT License + * Copyright (c) 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __HAL_LOG_H +#define __HAL_LOG_H + +#include <inttypes.h> +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define HAL_LOG_LEVEL_OFF -1 +#define HAL_LOG_LEVEL_INFO 0 +#define HAL_LOG_LEVEL_WARN 1 +#define HAL_LOG_LEVEL_ERROR 2 +#define _HAL_LOG_LEVEL_LAST 3 + +#ifndef HAL_LOG_LEVEL +#define HAL_LOG_LEVEL HAL_LOG_LEVEL_INFO +#endif + +#if HAL_LOG_LEVEL > HAL_LOG_LEVEL_OFF + +#if HAL_LOG_LEVEL <= HAL_LOG_LEVEL_INFO +#define HAL_LOG_INFO(...) HAL_Log(HAL_LOG_LEVEL_INFO, __func__, __VA_ARGS__) +#else +#define HAL_LOG_INFO(...) +#endif + +#if HAL_LOG_LEVEL <= HAL_LOG_LEVEL_WARN +#define HAL_LOG_WARN(...) HAL_Log(HAL_LOG_LEVEL_WARN, __func__, __VA_ARGS__) +#else +#define HAL_LOG_WARN(...) +#endif + +#if HAL_LOG_LEVEL <= HAL_LOG_LEVEL_ERROR +#define HAL_LOG_ERROR(...) HAL_Log(HAL_LOG_LEVEL_ERROR, __func__, __VA_ARGS__) +#else +#define HAL_LOG_ERROR(...) +#endif + +#else +#define HAL_LOG_INFO(...) +#define HAL_LOG_WARN(...) +#define HAL_LOG_ERROR(...) +#endif + +void HAL_Log_Init(void); +void HAL_Log_PrintString(const char* str); +void HAL_Log(uint8_t level, const char* func, const char* fmt, ...); +void HAL_Log_Printf(const char* fmt, ...); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/x_track/src/App/Service/HAL/HAL_Template.cpp b/x_track/src/App/Service/HAL/HAL_Template.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0afbacd047ddce8cff68f362b12159b6e8b43b65 --- /dev/null +++ b/x_track/src/App/Service/HAL/HAL_Template.cpp @@ -0,0 +1,73 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#if 0 + +#include "HAL.h" + +namespace HAL { + +class Template : private DeviceObject { +public: + Template(const char* name) + : DeviceObject(name) + { + } + +private: + virtual int onInit(); + virtual int onRead(void* buffer, size_t size); + virtual int onWrite(const void* buffer, size_t size); + virtual int onIoctl(DeviceObject::IO_Cmd_t cmd, void* data); +}; + +int Template::onInit() +{ + return DeviceObject::RES_OK; +} + +int Template::onRead(void* buffer, size_t size) +{ + return DeviceObject::RES_UNSUPPORT; +} + +int Template::onWrite(const void* buffer, size_t size) +{ + return DeviceObject::RES_UNSUPPORT; +} + +int Template::onIoctl(DeviceObject::IO_Cmd_t cmd, void* data) +{ + switch (cmd.full) { + case 0: + break; + default: + return DeviceObject::RES_UNSUPPORT; + } + return DeviceObject::RES_OK; +} + +} /* namespace HAL */ + +DEVICE_OBJECT_MAKE(Template); + +#endif diff --git a/x_track/src/App/Service/i18n/lv_i18n.c b/x_track/src/App/Service/i18n/lv_i18n.c new file mode 100644 index 0000000000000000000000000000000000000000..789b46d58a06f040be6a7e9ae985f397a7f70abb --- /dev/null +++ b/x_track/src/App/Service/i18n/lv_i18n.c @@ -0,0 +1,301 @@ +#include "./lv_i18n.h" + + +//////////////////////////////////////////////////////////////////////////////// +// Define plural operands +// http://unicode.org/reports/tr35/tr35-numbers.html#Operands + +// Integer version, simplified + +#ifndef UNUSED +#define UNUSED(x) (void)(x) +#endif + +static inline uint32_t op_n(int32_t val) { return (uint32_t)(val < 0 ? -val : val); } +static inline uint32_t op_i(uint32_t val) { return val; } +// always zero, when decimal part not exists. +static inline uint32_t op_v(uint32_t val) { UNUSED(val); return 0;} +static inline uint32_t op_w(uint32_t val) { UNUSED(val); return 0; } +static inline uint32_t op_f(uint32_t val) { UNUSED(val); return 0; } +static inline uint32_t op_t(uint32_t val) { UNUSED(val); return 0; } + +static lv_i18n_phrase_t en_us_singulars[] = { + {"EPHEMERIS", "Ephemeris"}, + {"LOAD_SUCCESS", "Load Success"}, + {"LOW_BATTERY", "Low Battery"}, + {"SHUTDOWN?", "Shutdown?"}, + {"NO_OPERATION", "No Operation"}, + {"NO", "No"}, + {"YES", "Yes"}, + {"START_RECORD", "Start Record"}, + {"STOP_RECORD", "Stop Record"}, + {"GNSS_NOT_READY", "GNSS Not Ready"}, + {"OPEN_FILE_FAILED", "Open File Failed"}, + {"AVG_SPEED", "AVG"}, + {"TIME", "Time"}, + {"DISTANCE", "Distance"}, + {"CALORIES", "Calories"}, + {"LOADING...", "Loading..."}, + {"POWER_OFF", "Power Off"}, + {"SPORT", "Sport"}, + {"GNSS", "GNSS"}, + {"BATTERY", "Battery"}, + {"ABOUT", "About"}, + {"TOTAL_TRIP", "Total Trip"}, + {"TOTAL_TIME", "Total Time"}, + {"MAX_SPEED", "Max Speed"}, + {"LATITUDE", "Latitude"}, + {"LONGITUDE", "Longitude"}, + {"ALTITUDE", "Altitude"}, + {"UTC_TIME", "UTC Time"}, + {"COURSE", "Course"}, + {"SPEED", "Speed"}, + {"DATE", "Date"}, + {"USAGE", "Usage"}, + {"VOLTAGE", "Voltage"}, + {"STATUS", "Status"}, + {"CHARGE", "Charge"}, + {"DISCHARGE", "Discharge"}, + {"NAME", "Name"}, + {"AUTHOR", "Author"}, + {"GRAPHICS", "GUI"}, + {"COMPILER", "Compiler"}, + {"BUILD_DATE", "Build"}, + {NULL, NULL} // End mark +}; + + + +static uint8_t en_us_plural_fn(int32_t num) +{ + uint32_t n = op_n(num); UNUSED(n); + uint32_t i = op_i(n); UNUSED(i); + uint32_t v = op_v(n); UNUSED(v); + + if ((i == 1 && v == 0)) return LV_I18N_PLURAL_TYPE_ONE; + return LV_I18N_PLURAL_TYPE_OTHER; +} + +static const lv_i18n_lang_t en_us_lang = { + .locale_name = "en-US", + .singulars = en_us_singulars, + + .locale_plural_fn = en_us_plural_fn +}; + +static lv_i18n_phrase_t zh_cn_singulars[] = { + {"EPHEMERIS", "星历"}, + {"LOAD_SUCCESS", "加载成功"}, + {"LOW_BATTERY", "低电量"}, + {"SHUTDOWN?", "关闭?"}, + {"NO_OPERATION", "无操作"}, + {"NO", "否"}, + {"YES", "是"}, + {"START_RECORD", "开始记录"}, + {"STOP_RECORD", "停止记录"}, + {"GNSS_NOT_READY", "GNSS 未就绪"}, + {"OPEN_FILE_FAILED", "打开文件失败"}, + {"AVG_SPEED", "平均速度"}, + {"TIME", "时间"}, + {"DISTANCE", "距离"}, + {"CALORIES", "卡路里"}, + {"LOADING...", "加载中..."}, + {"POWER_OFF", "关机"}, + {"SPORT", "运动"}, + {"GNSS", "卫星"}, + {"BATTERY", "电池"}, + {"ABOUT", "关于"}, + {"TOTAL_TRIP", "总里程"}, + {"TOTAL_TIME", "总时间"}, + {"MAX_SPEED", "最高速度"}, + {"LATITUDE", "纬度"}, + {"LONGITUDE", "经度"}, + {"ALTITUDE", "海拔"}, + {"UTC_TIME", "UTC 时间"}, + {"COURSE", "航向"}, + {"SPEED", "速度"}, + {"DATE", "日期"}, + {"USAGE", "使用量"}, + {"VOLTAGE", "电压"}, + {"STATUS", "状态"}, + {"CHARGE", "充电"}, + {"DISCHARGE", "放电"}, + {"NAME", "名称"}, + {"AUTHOR", "作者"}, + {"GRAPHICS", "图形"}, + {"COMPILER", "编译器"}, + {"BUILD_DATE", "编译日期"}, + {NULL, NULL} // End mark +}; + + + +static uint8_t zh_cn_plural_fn(int32_t num) +{ + + + + return LV_I18N_PLURAL_TYPE_OTHER; +} + +static const lv_i18n_lang_t zh_cn_lang = { + .locale_name = "zh-CN", + .singulars = zh_cn_singulars, + + .locale_plural_fn = zh_cn_plural_fn +}; + +const lv_i18n_language_pack_t lv_i18n_language_pack[] = { + &en_us_lang, + &zh_cn_lang, + NULL // End mark +}; + +//////////////////////////////////////////////////////////////////////////////// + + +// Internal state +static const lv_i18n_language_pack_t * current_lang_pack; +static const lv_i18n_lang_t * current_lang; + + +/** + * Reset internal state. For testing. + */ +void __lv_i18n_reset(void) +{ + current_lang_pack = NULL; + current_lang = NULL; +} + +/** + * Set the languages for internationalization + * @param langs pointer to the array of languages. (Last element has to be `NULL`) + */ +int lv_i18n_init(const lv_i18n_language_pack_t * langs) +{ + if(langs == NULL) return -1; + if(langs[0] == NULL) return -1; + + current_lang_pack = langs; + current_lang = langs[0]; /*Automatically select the first language*/ + return 0; +} + +/** + * Change the localization (language) + * @param l_name name of the translation locale to use. E.g. "en-GB" + */ +int lv_i18n_set_locale(const char * l_name) +{ + if(current_lang_pack == NULL) return -1; + + uint16_t i; + + for(i = 0; current_lang_pack[i] != NULL; i++) { + // Found -> finish + if(strcmp(current_lang_pack[i]->locale_name, l_name) == 0) { + current_lang = current_lang_pack[i]; + return 0; + } + } + + return -1; +} + + +static const char * __lv_i18n_get_text_core(lv_i18n_phrase_t * trans, const char * msg_id) +{ + uint16_t i; + for(i = 0; trans[i].msg_id != NULL; i++) { + if(strcmp(trans[i].msg_id, msg_id) == 0) { + /*The msg_id has found. Check the translation*/ + if(trans[i].translation) return trans[i].translation; + } + } + + return NULL; +} + + +/** + * Get the translation from a message ID + * @param msg_id message ID + * @return the translation of `msg_id` on the set local + */ +const char * lv_i18n_get_text(const char * msg_id) +{ + if(current_lang == NULL) return msg_id; + + const lv_i18n_lang_t * lang = current_lang; + const void * txt; + + // Search in current locale + if(lang->singulars != NULL) { + txt = __lv_i18n_get_text_core(lang->singulars, msg_id); + if (txt != NULL) return txt; + } + + // Try to fallback + if(lang == current_lang_pack[0]) return msg_id; + lang = current_lang_pack[0]; + + // Repeat search for default locale + if(lang->singulars != NULL) { + txt = __lv_i18n_get_text_core(lang->singulars, msg_id); + if (txt != NULL) return txt; + } + + return msg_id; +} + +/** + * Get the translation from a message ID and apply the language's plural rule to get correct form + * @param msg_id message ID + * @param num an integer to select the correct plural form + * @return the translation of `msg_id` on the set local + */ +const char * lv_i18n_get_text_plural(const char * msg_id, int32_t num) +{ + if(current_lang == NULL) return msg_id; + + const lv_i18n_lang_t * lang = current_lang; + const void * txt; + lv_i18n_plural_type_t ptype; + + // Search in current locale + if(lang->locale_plural_fn != NULL) { + ptype = lang->locale_plural_fn(num); + + if(lang->plurals[ptype] != NULL) { + txt = __lv_i18n_get_text_core(lang->plurals[ptype], msg_id); + if (txt != NULL) return txt; + } + } + + // Try to fallback + if(lang == current_lang_pack[0]) return msg_id; + lang = current_lang_pack[0]; + + // Repeat search for default locale + if(lang->locale_plural_fn != NULL) { + ptype = lang->locale_plural_fn(num); + + if(lang->plurals[ptype] != NULL) { + txt = __lv_i18n_get_text_core(lang->plurals[ptype], msg_id); + if (txt != NULL) return txt; + } + } + + return msg_id; +} + +/** + * Get the name of the currently used locale. + * @return name of the currently used locale. E.g. "en-GB" + */ +const char * lv_i18n_get_current_locale(void) +{ + if(!current_lang) return NULL; + return current_lang->locale_name; +} diff --git a/x_track/src/App/Service/i18n/lv_i18n.h b/x_track/src/App/Service/i18n/lv_i18n.h new file mode 100644 index 0000000000000000000000000000000000000000..9e803537cc784bb1e52e41d55092f6d43d15b307 --- /dev/null +++ b/x_track/src/App/Service/i18n/lv_i18n.h @@ -0,0 +1,85 @@ +#ifndef LV_I18N_H +#define LV_I18N_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> +#include <string.h> + +typedef enum { + LV_I18N_PLURAL_TYPE_ZERO, + LV_I18N_PLURAL_TYPE_ONE, + LV_I18N_PLURAL_TYPE_TWO, + LV_I18N_PLURAL_TYPE_FEW, + LV_I18N_PLURAL_TYPE_MANY, + LV_I18N_PLURAL_TYPE_OTHER, + _LV_I18N_PLURAL_TYPE_NUM, +} lv_i18n_plural_type_t; + +typedef struct { + const char * msg_id; + const char * translation; +} lv_i18n_phrase_t; + +typedef struct { + const char * locale_name; + lv_i18n_phrase_t * singulars; + lv_i18n_phrase_t * plurals[_LV_I18N_PLURAL_TYPE_NUM]; + uint8_t (*locale_plural_fn)(int32_t num); +} lv_i18n_lang_t; + +// Null-terminated list of languages. First one used as default. +typedef const lv_i18n_lang_t * lv_i18n_language_pack_t; + + +extern const lv_i18n_language_pack_t lv_i18n_language_pack[]; + + +/** + * Set the languages for internationalization + * @param langs pointer to the array of languages. (Last element has to be `NULL`) + */ +int lv_i18n_init(const lv_i18n_language_pack_t * langs); + +/** + * Change the localization (language) + * @param l_name name of the translation locale to use. E.g. "en_GB" + */ +int lv_i18n_set_locale(const char * l_name); + +/** + * Get the translation from a message ID + * @param msg_id message ID + * @return the translation of `msg_id` on the set local + */ +const char * lv_i18n_get_text(const char * msg_id); + +/** + * Get the translation from a message ID and apply the language's plural rule to get correct form + * @param msg_id message ID + * @param num an integer to select the correct plural form + * @return the translation of `msg_id` on the set local + */ +const char * lv_i18n_get_text_plural(const char * msg_id, int32_t num); + +/** + * Get the name of the currently used localization. + * @return name of the currently used localization. E.g. "en_GB" + */ +const char * lv_i18n_get_current_locale(void); + + +void __lv_i18n_reset(void); + + +#define _(text) lv_i18n_get_text(text) +#define _p(text, num) lv_i18n_get_text_plural(text, num) + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*LV_LANG_H*/ diff --git a/x_track/src/App/Service/i18n/translations/en-US.yml b/x_track/src/App/Service/i18n/translations/en-US.yml new file mode 100644 index 0000000000000000000000000000000000000000..a1e543061c4d1e7a9010e939343c5eac0804ec96 --- /dev/null +++ b/x_track/src/App/Service/i18n/translations/en-US.yml @@ -0,0 +1,42 @@ +en-US: + EPHEMERIS: Ephemeris + LOAD_SUCCESS: Load Success + LOW_BATTERY: Low Battery + SHUTDOWN?: Shutdown? + NO_OPERATION: No Operation + 'NO': 'No' + 'YES': 'Yes' + START_RECORD: Start Record + STOP_RECORD: Stop Record + GNSS_NOT_READY: GNSS Not Ready + OPEN_FILE_FAILED: Open File Failed + AVG_SPEED: AVG + TIME: Time + DISTANCE: Distance + CALORIES: Calories + LOADING...: Loading... + POWER_OFF: Power Off + SPORT: Sport + GNSS: GNSS + BATTERY: Battery + ABOUT: About + TOTAL_TRIP: Total Trip + TOTAL_TIME: Total Time + MAX_SPEED: Max Speed + LATITUDE: Latitude + LONGITUDE: Longitude + ALTITUDE: Altitude + UTC_TIME: UTC Time + COURSE: Course + SPEED: Speed + DATE: Date + USAGE: Usage + VOLTAGE: Voltage + STATUS: Status + CHARGE: Charge + DISCHARGE: Discharge + NAME: Name + AUTHOR: Author + GRAPHICS: GUI + COMPILER: Compiler + BUILD_DATE: Build diff --git a/x_track/src/App/Service/i18n/translations/zh-CN.yml b/x_track/src/App/Service/i18n/translations/zh-CN.yml new file mode 100644 index 0000000000000000000000000000000000000000..28fb58f844eb5072447058d508d3bf201c7ce5e1 --- /dev/null +++ b/x_track/src/App/Service/i18n/translations/zh-CN.yml @@ -0,0 +1,42 @@ +zh-CN: + EPHEMERIS: 星历 + LOAD_SUCCESS: 加载成功 + LOW_BATTERY: 低电量 + SHUTDOWN?: 关闭? + NO_OPERATION: 无操作 + 'NO': 否 + 'YES': 是 + START_RECORD: 开始记录 + STOP_RECORD: 停止记录 + GNSS_NOT_READY: GNSS 未就绪 + OPEN_FILE_FAILED: 打开文件失败 + AVG_SPEED: 平均速度 + TIME: 时间 + DISTANCE: 距离 + CALORIES: 卡路里 + LOADING...: 加载中... + POWER_OFF: 关机 + SPORT: 运动 + GNSS: 卫星 + BATTERY: 电池 + ABOUT: 关于 + TOTAL_TRIP: 总里程 + TOTAL_TIME: 总时间 + MAX_SPEED: 最高速度 + LATITUDE: 纬度 + LONGITUDE: 经度 + ALTITUDE: 海拔 + UTC_TIME: UTC 时间 + COURSE: 航向 + SPEED: 速度 + DATE: 日期 + USAGE: 使用量 + VOLTAGE: 电压 + STATUS: 状态 + CHARGE: 充电 + DISCHARGE: 放电 + NAME: 名称 + AUTHOR: 作者 + GRAPHICS: 图形 + COMPILER: 编译器 + BUILD_DATE: 编译日期 diff --git a/x_track/src/App/Service/i18n/update_translation.sh b/x_track/src/App/Service/i18n/update_translation.sh new file mode 100755 index 0000000000000000000000000000000000000000..eb139595158bf93b01ddd33c99897f2a93066936 --- /dev/null +++ b/x_track/src/App/Service/i18n/update_translation.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Check if lv_i18n is installed +if npm list -g lv_i18n >/dev/null 2>&1; then + echo "lv_i18n is already installed." +else + echo "lv_i18n is not installed. Please run the following command to install it:" + echo "npm install -g littlevgl/lv_i18n" + exit -1 +fi + +lv_i18n extract -s '../../**/*.+(c|cpp|h|hpp)' -t './translations/*.yml' +lv_i18n compile -t './translations/*.yml' -o './' diff --git a/x_track/src/App/UI/AppFactory.cpp b/x_track/src/App/UI/AppFactory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fae76d76f85828a7ecc8b57647f86984f88067cd --- /dev/null +++ b/x_track/src/App/UI/AppFactory.cpp @@ -0,0 +1,62 @@ +/* + * MIT License + * Copyright (c) 2021 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "AppFactory.h" +#include <cstring> + +AppFactory::AppFactory() + : _nameArray { 0 } + , _descriptorArray { 0 } + , _num(0) +{ +} + +AppFactory::~AppFactory() { } + +void AppFactory::add(const char* name, AppDescriptor* descriptor) +{ + if (_num >= APP_FACTORY_MAX_NUM) { + return; + } + + _nameArray[_num] = name; + _descriptorArray[_num] = descriptor; + _num++; +} + +PageBase* AppFactory::create(const char* name) +{ + for (int i = 0; i < _num; i++) { + if (strcmp(name, _nameArray[i]) == 0) { + return _descriptorArray[i]->create(); + } + } + + return nullptr; +} + +void AppFactory::intsallAll(PageManager* manager) +{ + for (int i = 0; i < _num; i++) { + manager->install(_nameArray[i], nullptr); + } +} diff --git a/x_track/src/App/UI/AppFactory.h b/x_track/src/App/UI/AppFactory.h new file mode 100644 index 0000000000000000000000000000000000000000..08add7920aac7a6f0ddf8362ab675e6e9f66a464 --- /dev/null +++ b/x_track/src/App/UI/AppFactory.h @@ -0,0 +1,81 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __APP_FACTORY_H +#define __APP_FACTORY_H + +#include "Frameworks/PageManager/PageManager.h" + +#ifndef APP_FACTORY_MAX_NUM +#define APP_FACTORY_MAX_NUM 32 +#endif + +#define APP_DESCRIPTOR_DEF(name) \ + class name##Descriptor : public AppDescriptor { \ + public: \ + name##Descriptor() \ + : AppDescriptor(#name) \ + { \ + } \ + virtual PageBase* create() { return new name; } \ + }; \ + static name##Descriptor name##DescriptorInstance; \ + void name##Constructor() { (void)&name##DescriptorInstance; } + +/* Force reference to DescriptorInstance to prevent optimization */ +#define APP_DESCRIPTOR_IMPORT(name) \ + extern void name##Constructor(); \ + name##Constructor(); + +class AppDescriptor; + +class AppFactory : public PageFactory { +public: + AppFactory(); + virtual ~AppFactory(); + virtual PageBase* create(const char* name); + void add(const char* name, AppDescriptor* descriptor); + + static AppFactory* getInstance() + { + static AppFactory instance; + return &instance; + } + + void intsallAll(PageManager* manager); + +private: + const char* _nameArray[APP_FACTORY_MAX_NUM]; + AppDescriptor* _descriptorArray[APP_FACTORY_MAX_NUM]; + int _num; +}; + +class AppDescriptor { +public: + AppDescriptor(const char* name) + { + AppFactory::getInstance()->add(name, this); + } + virtual PageBase* create() = 0; +}; + +#endif /* __APP_FACTORY_H */ diff --git a/x_track/src/App/UI/Dashboard/BindingDef.inc b/x_track/src/App/UI/Dashboard/BindingDef.inc new file mode 100644 index 0000000000000000000000000000000000000000..9c36fa55a29f4cc9e4693678decc455dd0cdaa52 --- /dev/null +++ b/x_track/src/App/UI/Dashboard/BindingDef.inc @@ -0,0 +1,3 @@ +/* Binding definition */ + +BINDING_DEF(Rec, bool) diff --git a/x_track/src/App/UI/Dashboard/Dashboard.cpp b/x_track/src/App/UI/Dashboard/Dashboard.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d1975b37c06cf173a5e8ed77940f370a5cac8a05 --- /dev/null +++ b/x_track/src/App/UI/Dashboard/Dashboard.cpp @@ -0,0 +1,124 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Dashboard.h" +#include <cmath> + +using namespace Page; + +APP_DESCRIPTOR_DEF(Dashboard); + +Dashboard::Dashboard() + : _model(nullptr) + , _view(nullptr) +{ +} + +Dashboard::~Dashboard() +{ +} + +void Dashboard::onInstalled() +{ + setLoadAnimType(PAGE_ANIM::NONE); +} + +void Dashboard::onViewLoad() +{ + _model = new DashboardModel(this); + _view = new DashboardView(this, getRoot()); +} + +void Dashboard::onViewDidLoad() +{ +} + +void Dashboard::onViewWillAppear() +{ +} + +void Dashboard::onViewDidAppear() +{ + Param_t param; + param.animEnable = false, + + PAGE_GET_PARAM(param); + if (param.animEnable) { + _view->publish(DashboardView::MSG_ID::ANIM_START); + } +} + +void Dashboard::onViewWillDisappear() +{ +} + +void Dashboard::onViewDidDisappear() +{ +} + +void Dashboard::onViewUnload() +{ + delete _model; + delete _view; +} + +void Dashboard::onViewDidUnload() +{ +} + +void Dashboard::onModelEvent(DashboardModel::EVENT_ID id, const void* param) +{ + switch (id) { + case DashboardModel::EVENT_ID::SPORT_STATUS: { + _view->publish(DashboardView::MSG_ID::SPORT_STATUS, param); + } break; + + case DashboardModel::EVENT_ID::RECORDER_STATUS: { + _view->publish(DashboardView::MSG_ID::RECORDER_STATUS, param); + } break; + + case DashboardModel::EVENT_ID::GNSS: { + _view->publish(DashboardView::MSG_ID::GNSS_STATUS, param); + } break; + + default: + break; + } +} + +void Dashboard::onViewEvent(DashboardView::EVENT_ID id, const void* param) +{ + switch (id) { + case DashboardView::EVENT_ID::GET_BINDING: { + auto binding = (DashboardView::Binding_Info_t*)param; + binding->binding = _model->getBinding((DashboardModel::BINDING_TYPE)binding->type); + } break; + + case DashboardView::EVENT_ID::NAVI_TO_PAGE: { + auto pageID = (const char*)param; + getManager()->push(pageID); + }; break; + + default: + break; + } +} diff --git a/x_track/src/App/UI/Dashboard/Dashboard.h b/x_track/src/App/UI/Dashboard/Dashboard.h new file mode 100644 index 0000000000000000000000000000000000000000..203e13a2a335db0131f6c596cb59ae0adfcdf422 --- /dev/null +++ b/x_track/src/App/UI/Dashboard/Dashboard.h @@ -0,0 +1,62 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DASHBOARD_PRESENTER_H +#define __DASHBOARD_PRESENTER_H + +#include "DashboardModel.h" +#include "DashboardView.h" + +namespace Page { + +class Dashboard : public PageBase, public DashboardModel::EventListener, public DashboardView::EventListener { +public: + typedef struct { + bool animEnable; + } Param_t; + +public: + Dashboard(); + virtual ~Dashboard(); + + virtual void onInstalled(); + virtual void onViewLoad(); + virtual void onViewDidLoad(); + virtual void onViewWillAppear(); + virtual void onViewDidAppear(); + virtual void onViewWillDisappear(); + virtual void onViewDidDisappear(); + virtual void onViewUnload(); + virtual void onViewDidUnload(); + +private: + virtual void onModelEvent(DashboardModel::EVENT_ID id, const void* param) override; + virtual void onViewEvent(DashboardView::EVENT_ID id, const void* param) override; + +private: + DashboardModel* _model; + DashboardView* _view; +}; + +} + +#endif diff --git a/x_track/src/App/UI/Dashboard/DashboardModel.cpp b/x_track/src/App/UI/Dashboard/DashboardModel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..50a0626c86dffb2b674c16f0b2f7a81a2fd89cd0 --- /dev/null +++ b/x_track/src/App/UI/Dashboard/DashboardModel.cpp @@ -0,0 +1,84 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "DashboardModel.h" + +using namespace Page; + +DashboardModel::DashboardModel(EventListener* listener) + : DataNode(__func__, DataProc::broker()) + , _listener(listener) + , _env(this) +{ + _nodeSportStatus = subscribe("SportStatus"); + _nodeRecorder = subscribe("Recorder"); + _nodeGNSS = subscribe("GNSS"); + setEventFilter(DataNode::EVENT_PUBLISH); +} + +DashboardModel::~DashboardModel() +{ +} + +void DashboardModel::initBingdings() +{ + _bindingRec.setCallback( + /* setter */ + [](DashboardModel* self, bool v) { + DataProc::Recorder_Info_t info; + info.active = v; + self->notify(self->_nodeRecorder, &info, sizeof(info)); + }, + /* getter */ + [](DashboardModel* self) -> bool { + DataProc::Recorder_Info_t info; + self->pull(self->_nodeRecorder, &info, sizeof(info)); + return info.active; + }, + this); +} + +void* DashboardModel::getBinding(BINDING_TYPE type) +{ + switch (type) { +#define BINDING_DEF(name, type) \ + case BINDING_TYPE::name: \ + return &_binding##name; +#include "BindingDef.inc" +#undef BINDING_DEF + default: + return nullptr; + } +} + +int DashboardModel::onEvent(DataNode::EventParam_t* param) +{ + if (param->tran == _nodeSportStatus) { + _listener->onModelEvent(EVENT_ID::SPORT_STATUS, param->data_p); + } else if (param->tran == _nodeRecorder) { + _listener->onModelEvent(EVENT_ID::RECORDER_STATUS, param->data_p); + } else if (param->tran == _nodeGNSS) { + _listener->onModelEvent(EVENT_ID::GNSS, param->data_p); + } + + return DataNode::RES_OK; +} diff --git a/x_track/src/App/UI/Dashboard/DashboardModel.h b/x_track/src/App/UI/Dashboard/DashboardModel.h new file mode 100644 index 0000000000000000000000000000000000000000..807cb8009e7196c58d1c5c688654bd3bce64d42e --- /dev/null +++ b/x_track/src/App/UI/Dashboard/DashboardModel.h @@ -0,0 +1,84 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DASHBOARD_MODEL_H +#define __DASHBOARD_MODEL_H + +#include "Service/DataProc/DataProc.h" +#include "Utils/Binding/Binding.h" + +namespace Page { + +class DashboardModel : private DataNode { + +public: + enum class EVENT_ID { + SPORT_STATUS, /* param: SportStatus_Info_t */ + RECORDER_STATUS, /* param: Recorder_Info_t */ + GNSS, /* param: GNSS_Info_t */ + _EVENT_LAST, + }; + + enum class BINDING_TYPE { +#define BINDING_DEF(name, type) name, +#include "BindingDef.inc" +#undef BINDING_DEF + }; + + typedef struct { + BINDING_TYPE type; + void* binding; + } Binding_Info_t; + + class EventListener { + public: + virtual void onModelEvent(EVENT_ID id, const void* param = nullptr) = 0; + }; + +public: + DashboardModel(EventListener* listener); + ~DashboardModel(); + void initBingdings(); + void* getBinding(BINDING_TYPE type); + DataProc::Env_Helper* env() + { + return &_env; + } + +private: + EventListener* _listener; + const DataNode* _nodeSportStatus; + const DataNode* _nodeRecorder; + const DataNode* _nodeGNSS; + DataProc::Env_Helper _env; + +#define BINDING_DEF(name, type) Binding<type, DashboardModel> _binding##name; +#include "BindingDef.inc" +#undef BINDING_DEF + +private: + virtual int onEvent(DataNode::EventParam_t* param); +}; + +} /* namespace Page */ + +#endif diff --git a/x_track/src/App/UI/Dashboard/DashboardView.cpp b/x_track/src/App/UI/Dashboard/DashboardView.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dad123f84a9dce6e18393cbb9e53a4974540da00 --- /dev/null +++ b/x_track/src/App/UI/Dashboard/DashboardView.cpp @@ -0,0 +1,397 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "DashboardView.h" +#include "Service/DataProc/DataProc_Def.h" +#include "Service/HAL/HAL_Def.h" +#include "Utils/Geo/Geo.h" +#include "Utils/lv_ext/lv_anim_timeline_wrapper.h" +#include "Utils/lv_ext/lv_ext_func.h" +#include <stdarg.h> +#include <stdio.h> + +using namespace Page; + +DashboardView::DashboardView(EventListener* listener, lv_obj_t* root) + : _listener(listener) + , _fontLarge(65, "regular") +#define BINDING_DEF(name, type) , _binding##name((Binding<type, DashboardModel>*)getBinding(BINDING_TYPE::name)) +#include "BindingDef.inc" +#undef BINDING_DEF +{ + lv_obj_set_style_pad_all(root, 0, 0); + + lv_obj_t* topInfoCont = topInfoCreate(root); + lv_obj_t* bottomInfoCont = bottomInfoCreate(root); + lv_obj_t* btnGrpCont = btnGroupCreate(root); + lv_obj_move_foreground(topInfoCont); + + _anim_timeline = lv_anim_timeline_create(); + +#define ANIM_DEF(start_time, obj, attr, start, end) \ + { \ + start_time, obj, LV_ANIM_EXEC(attr), start, end, 500, lv_anim_path_ease_out, true \ + } + + auto lv_obj_set_opa = [](void* obj, int32_t v) { + lv_obj_set_style_opa((lv_obj_t*)obj, v, 0); + }; + + auto lv_obj_set_trans_y = [](void* obj, int32_t v) { + lv_obj_set_style_translate_y((lv_obj_t*)obj, v, 0); + }; + + auto lv_obj_set_trans_h = [](void* obj, int32_t v) { + lv_obj_set_style_transform_height((lv_obj_t*)obj, v, 0); + }; + + auto lv_obj_set_text_opa = [](void* obj, int32_t v) { + lv_obj_set_style_text_opa((lv_obj_t*)obj, v, 0); + }; + + lv_obj_update_layout(root); + lv_coord_t btn_h = lv_obj_get_height(lv_obj_get_child(btnGrpCont, 0)); + + lv_anim_timeline_wrapper_t wrapper[] = { + ANIM_DEF(0, topInfoCont, trans_y, -lv_obj_get_height(topInfoCont), 0), + ANIM_DEF(200, bottomInfoCont, trans_y, -lv_obj_get_height(bottomInfoCont), 0), + ANIM_DEF(200, bottomInfoCont, opa, LV_OPA_TRANSP, LV_OPA_COVER), + + ANIM_DEF(500, lv_obj_get_child(btnGrpCont, 0), trans_h, -btn_h, 0), + ANIM_DEF(600, lv_obj_get_child(btnGrpCont, 1), trans_h, -btn_h, 0), + ANIM_DEF(700, lv_obj_get_child(btnGrpCont, 2), trans_h, -btn_h, 0), + ANIM_DEF(900, btnGrpCont, text_opa, LV_OPA_TRANSP, LV_OPA_COVER), + LV_ANIM_TIMELINE_WRAPPER_END + }; + lv_anim_timeline_add_wrapper(_anim_timeline, wrapper); + lv_anim_timeline_set_progress(_anim_timeline, 0); + + subscribe( + MSG_ID::ANIM_START, + root, + [](lv_event_t* e) { + auto self = (DashboardView*)lv_event_get_user_data(e); + auto msg = lv_event_get_msg(e); + if (lv_msg_get_id(msg) != self->msgID(MSG_ID::ANIM_START)) { + return; + } + + lv_anim_timeline_start(self->_anim_timeline); + }, + this); +} + +DashboardView::~DashboardView() +{ +} + +lv_uintptr_t DashboardView::msgID(MSG_ID id) +{ + return (lv_uintptr_t)this + (lv_uintptr_t)id; +} + +void DashboardView::publish(MSG_ID id, const void* payload) +{ + lv_msg_send(msgID(id), payload); +} + +void DashboardView::subscribe(MSG_ID id, lv_obj_t* obj, lv_event_cb_t event_cb, void* user_data) +{ + lv_msg_subscribe_obj(msgID(id), obj, this); + lv_obj_add_event(obj, event_cb, LV_EVENT_MSG_RECEIVED, user_data); +} + +void* DashboardView::getBinding(BINDING_TYPE type) +{ + Binding_Info_t info; + info.type = type; + info.binding = nullptr; + _listener->onViewEvent(EVENT_ID::GET_BINDING, &info); + return info.binding; +} + +lv_obj_t* DashboardView::topInfoCreate(lv_obj_t* par) +{ + lv_obj_t* cont = lv_obj_create(par); + { + lv_obj_remove_style_all(cont); + lv_obj_set_size(cont, lv_pct(100), 135); + + lv_obj_set_style_bg_opa(cont, LV_OPA_COVER, 0); + lv_obj_set_style_bg_color(cont, lv_color_hex(0x333333), 0); + + lv_obj_set_style_radius(cont, 27, 0); + lv_obj_align(cont, LV_ALIGN_TOP_MID, 0, -27); + + lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align( + cont, + LV_FLEX_ALIGN_END, + LV_FLEX_ALIGN_CENTER, + LV_FLEX_ALIGN_CENTER); + } + + { + lv_obj_t* label = lv_label_create(cont); + lv_obj_set_style_text_font(label, _fontLarge, 0); + lv_obj_set_style_text_color(label, lv_color_white(), 0); + lv_obj_set_style_translate_y(label, 10, 0); + lv_label_set_text_static(label, "00"); + + subscribe( + MSG_ID::SPORT_STATUS, + label, + [](lv_event_t* e) { + auto msg = lv_event_get_msg(e); + auto info = (const DataProc::SportStatus_Info_t*)lv_msg_get_payload(msg); + auto obj = lv_event_get_current_target_obj(e); + lv_label_set_text_fmt(obj, "%02d", (int)info->speedKph); + }); + } + + { + lv_obj_t* label = lv_label_create(cont); + lv_obj_set_style_text_color(label, lv_color_white(), 0); + lv_obj_set_style_translate_y(label, -5, 0); + lv_label_set_text_static(label, "km/h"); + } + + return cont; +} + +lv_obj_t* DashboardView::bottomInfoCreate(lv_obj_t* par) +{ + lv_obj_t* cont = lv_obj_create(par); + lv_obj_remove_style_all(cont); + lv_obj_set_size(cont, lv_pct(100), 160); + lv_obj_align(cont, LV_ALIGN_TOP_MID, 0, 110); + + lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW_WRAP); + lv_obj_set_flex_align( + cont, + LV_FLEX_ALIGN_SPACE_EVENLY, + LV_FLEX_ALIGN_CENTER, + LV_FLEX_ALIGN_SPACE_EVENLY); + + { + lv_obj_t* label = infoItemCreate(cont, _("AVG_SPEED")); + subscribe( + MSG_ID::SPORT_STATUS, + label, + [](lv_event_t* e) { + auto msg = lv_event_get_msg(e); + auto info = (const DataProc::SportStatus_Info_t*)lv_msg_get_payload(msg); + auto obj = lv_event_get_current_target_obj(e); + lv_label_set_text_fmt(obj, "%0.1f km/h", info->speedAvgKph); + }); + } + + { + lv_obj_t* label = infoItemCreate(cont, _("TIME")); + subscribe( + MSG_ID::SPORT_STATUS, + label, + [](lv_event_t* e) { + auto msg = lv_event_get_msg(e); + auto info = (const DataProc::SportStatus_Info_t*)lv_msg_get_payload(msg); + auto obj = lv_event_get_current_target_obj(e); + lv_label_set_text_fmt(obj, "%s", makeTimeString(info->singleTime)); + }); + } + + { + lv_obj_t* label = infoItemCreate(cont, _("DISTANCE")); + subscribe( + MSG_ID::SPORT_STATUS, + label, + [](lv_event_t* e) { + auto msg = lv_event_get_msg(e); + auto info = (const DataProc::SportStatus_Info_t*)lv_msg_get_payload(msg); + auto obj = lv_event_get_current_target_obj(e); + lv_label_set_text_fmt(obj, "%0.1f km", info->singleDistance / 1000.0f); + }); + } + + { + lv_obj_t* label = infoItemCreate(cont, _("ALTITUDE")); + subscribe( + MSG_ID::SPORT_STATUS, + label, + [](lv_event_t* e) { + auto msg = lv_event_get_msg(e); + auto info = (const DataProc::SportStatus_Info_t*)lv_msg_get_payload(msg); + auto obj = lv_event_get_current_target_obj(e); + lv_label_set_text_fmt(obj, "%0.1f m", info->altitude); + }); + } + + { + lv_obj_t* label = infoItemCreate(cont, _("CALORIES")); + subscribe( + MSG_ID::SPORT_STATUS, + label, + [](lv_event_t* e) { + auto msg = lv_event_get_msg(e); + auto info = (const DataProc::SportStatus_Info_t*)lv_msg_get_payload(msg); + auto obj = lv_event_get_current_target_obj(e); + lv_label_set_text_fmt(obj, "%d k", (int)info->singleCalorie); + }); + } + + { + lv_obj_t* label = infoItemCreate(cont, _("COURSE")); + subscribe( + MSG_ID::GNSS_STATUS, + label, + [](lv_event_t* e) { + auto msg = lv_event_get_msg(e); + auto info = (const HAL::GNSS_Info_t*)lv_msg_get_payload(msg); + auto obj = lv_event_get_current_target_obj(e); + lv_label_set_text_fmt(obj, "%s", Geo::cardinal(info->course)); + }); + } + + return cont; +} + +lv_obj_t* DashboardView::infoItemCreate(lv_obj_t* par, const char* title) +{ + lv_obj_t* cont = lv_obj_create(par); + lv_obj_remove_style_all(cont); + lv_obj_remove_flag(cont, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_size(cont, 100, 39); + + lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align( + cont, + LV_FLEX_ALIGN_SPACE_AROUND, + LV_FLEX_ALIGN_CENTER, + LV_FLEX_ALIGN_CENTER); + + lv_obj_t* labelValue = lv_label_create(cont); + lv_label_set_text_static(labelValue, "-"); + + { + lv_obj_t* label = lv_label_create(cont); + lv_obj_set_style_text_color(label, lv_color_hex(0xb3b3b3), 0); + lv_label_set_text(label, title); + } + + return labelValue; +} + +lv_obj_t* DashboardView::btnGroupCreate(lv_obj_t* par) +{ + lv_obj_t* cont = lv_obj_create(par); + lv_obj_remove_style_all(cont); + lv_obj_set_size(cont, lv_pct(100), 40); + lv_obj_align(cont, LV_ALIGN_BOTTOM_MID, 0, -10); + + lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align( + cont, + LV_FLEX_ALIGN_SPACE_AROUND, + LV_FLEX_ALIGN_CENTER, + LV_FLEX_ALIGN_CENTER); + + btnCreate(cont, LV_SYMBOL_EXT_LOCATION_DOT, "LiveMap"); + btnCreate(cont, LV_SYMBOL_EXT_POWER_OFF, "Shutdown"); + btnCreate(cont, LV_SYMBOL_EXT_BARS, "SystemInfos"); + + return cont; +} + +lv_obj_t* DashboardView::btnCreate(lv_obj_t* par, const char* symbol, const char* pageID) +{ + lv_obj_t* btn = lv_obj_create(par); + lv_obj_remove_style_all(btn); + lv_obj_set_size(btn, 40, 31); + lv_obj_clear_flag(btn, LV_OBJ_FLAG_SCROLLABLE); + + lv_obj_t* label = lv_label_create(btn); + lv_label_set_text_static(label, symbol); + lv_obj_center(label); + + lv_obj_set_style_bg_opa(btn, LV_OPA_COVER, 0); + lv_obj_set_style_transform_width(btn, 5, LV_STATE_PRESSED); + lv_obj_set_style_transform_height(btn, -5, LV_STATE_PRESSED); + lv_obj_set_style_bg_color(btn, lv_color_hex(0x666666), 0); + lv_obj_set_style_bg_color(btn, lv_color_hex(0xbbbbbb), LV_STATE_PRESSED); + lv_obj_set_style_bg_color(btn, lv_color_hex(0xff931e), LV_STATE_FOCUSED); + lv_obj_set_style_radius(btn, 9, 0); + + static lv_style_transition_dsc_t tran; + static const lv_style_prop_t prop[] = { + LV_STYLE_TRANSFORM_WIDTH, + LV_STYLE_TRANSFORM_HEIGHT, + LV_STYLE_PROP_INV + }; + lv_style_transition_dsc_init( + &tran, + prop, + lv_anim_path_ease_out, + 200, + 0, + nullptr); + lv_obj_set_style_transition(btn, &tran, LV_STATE_PRESSED); + lv_obj_set_style_transition(btn, &tran, LV_STATE_FOCUSED); + + if (pageID) { + lv_obj_set_user_data(btn, (void*)pageID); + lv_obj_add_event_cb( + btn, + [](lv_event_t* e) { + auto self = (DashboardView*)lv_event_get_user_data(e); + auto id = lv_obj_get_user_data(lv_event_get_current_target_obj(e)); + self->_listener->onViewEvent(EVENT_ID::NAVI_TO_PAGE, id); + }, + LV_EVENT_CLICKED, + this); + } + + return btn; +} + +const char* DashboardView::makeTimeString(uint64_t ms) +{ + static char buf[16]; + uint64_t ss = ms / 1000; + uint64_t mm = ss / 60; + uint32_t hh = (uint32_t)(mm / 60); + + if (hh < 100) { + lv_snprintf( + buf, sizeof(buf), + "%d:%02d:%02d", + (int)hh, + (int)(mm % 60), + (int)(ss % 60)); + } else { + lv_snprintf( + buf, sizeof(buf), + "%d:%02d", + (int)hh, + (int)(mm % 60)); + } + + return buf; +} diff --git a/x_track/src/App/UI/Dashboard/DashboardView.h b/x_track/src/App/UI/Dashboard/DashboardView.h new file mode 100644 index 0000000000000000000000000000000000000000..ba7acac346cd6261e16d14484daad8f59383f55a --- /dev/null +++ b/x_track/src/App/UI/Dashboard/DashboardView.h @@ -0,0 +1,98 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DASHBOARD_VIEW_H +#define __DASHBOARD_VIEW_H + +#include "../Page.h" +#include "Utils/Binding/Binding.h" + +namespace Page { + +class DashboardModel; + +class DashboardView { +public: + enum class EVENT_ID { + GET_BINDING, /* Param: Binding_Info_t */ + NAVI_TO_PAGE, /* Param: const char* */ + _LAST, + }; + + enum class MSG_ID { + NONE, + SPORT_STATUS, /* param: SportStatus_Info_t */ + RECORDER_STATUS, /* param: Recorder_Info_t */ + GNSS_STATUS, /* param: GNSS_Info_t */ + ANIM_START, /* param: None */ + _LAST + }; + + enum class BINDING_TYPE { +#define BINDING_DEF(name, type) name, +#include "BindingDef.inc" +#undef BINDING_DEF + }; + + typedef struct { + BINDING_TYPE type; + void* binding; + } Binding_Info_t; + + class EventListener { + public: + virtual void onViewEvent(EVENT_ID id, const void* param = nullptr) = 0; + }; + +public: + DashboardView(EventListener* listener, lv_obj_t* root); + ~DashboardView(); + void publish(MSG_ID id, const void* payload = nullptr); + +private: + EventListener* _listener; + ResourcePool::Font _fontLarge; + lv_anim_timeline_t* _anim_timeline; + +#define BINDING_DEF(name, type) Binding<type, DashboardModel>* _binding##name; +#include "BindingDef.inc" +#undef BINDING_DEF + +private: + lv_uintptr_t msgID(MSG_ID id); + void subscribe(MSG_ID id, lv_obj_t* obj, lv_event_cb_t cb, void* user_data = nullptr); + void* getBinding(BINDING_TYPE type); + + lv_obj_t* topInfoCreate(lv_obj_t* par); + + lv_obj_t* bottomInfoCreate(lv_obj_t* par); + lv_obj_t* infoItemCreate(lv_obj_t* par, const char* title); + + lv_obj_t* btnGroupCreate(lv_obj_t* par); + lv_obj_t* btnCreate(lv_obj_t* par, const char* symbol, const char* pageID = nullptr); + + static const char* makeTimeString(uint64_t ms); +}; + +} + +#endif /* __DASHBOARD_VIEW_H */ diff --git a/x_track/src/App/UI/LiveMap/LiveMap.cpp b/x_track/src/App/UI/LiveMap/LiveMap.cpp new file mode 100644 index 0000000000000000000000000000000000000000..107fe88352a9c6e86bf86035fc9255f4d12776da --- /dev/null +++ b/x_track/src/App/UI/LiveMap/LiveMap.cpp @@ -0,0 +1,161 @@ +/* + * MIT License + * Copyright (c) 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "LiveMap.h" + +using namespace Page; + +APP_DESCRIPTOR_DEF(LiveMap); + +LiveMap::LiveMap() + : _model(nullptr) + , _view(nullptr) +{ +} + +LiveMap::~LiveMap() +{ +} + +void LiveMap::onInstalled() +{ + /* workaround page is hidden after the model is not disconnected */ + setAutoCacheEnable(false); + + setBackGestureDirection(LV_DIR_RIGHT); +} + +void LiveMap::onViewLoad() +{ + _model = new LiveMapModel(this); + _view = new LiveMapView(this, getRoot()); + + DataProc::Map_Info_t mapInfo; + if (_model->getMapInfo(&mapInfo)) { + MapView::MapConfig_t config; + config.path = mapInfo.path; + config.ext = mapInfo.ext; + config.levelMin = mapInfo.levelMin; + config.levelMax = mapInfo.levelMax; + config.coordTrans = mapInfo.coordTrans; + _view->publish(LiveMapView::MSG_ID::MAP_CONFIG_UPDATE, &config); + } +} + +void LiveMap::onViewDidLoad() +{ +} + +void LiveMap::onViewWillAppear() +{ +} + +void LiveMap::onViewDidAppear() +{ + _model->env()->set("statusbar-opa", "light"); + + _model->loadSportStatusInfo(); + + int mapLevel = _model->getMapLevel(); + onModelEvent(LiveMapModel::EVENT_ID::MAP_LEVEL_CHANGE, &mapLevel); + + DataProc::TrackFilter_Info_t trackFilterInfo; + if (_model->getTrackFilterInfo(&trackFilterInfo)) { + onModelEvent(LiveMapModel::EVENT_ID::TRACK_REC, &trackFilterInfo); + } + + HAL::GNSS_Info_t gnssInfo; + if (_model->getGNSSInfo(&gnssInfo)) { + onModelEvent(LiveMapModel::EVENT_ID::GNSS, &gnssInfo); + + /* enable gnss event */ + _model->setGNSSEnable(true); + } +} + +void LiveMap::onViewWillDisappear() +{ + /* disable gnss event */ + _model->setGNSSEnable(false); + + int mapLevel = _model->getMapLevel(); + char buf[8]; + lv_snprintf(buf, sizeof(buf), "%d", mapLevel); + _model->env()->set("maplevel", buf); +} + +void LiveMap::onViewDidDisappear() +{ +} + +void LiveMap::onViewUnload() +{ + delete _model; + delete _view; +} + +void LiveMap::onViewDidUnload() +{ +} + +void LiveMap::onModelEvent(LiveMapModel::EVENT_ID id, const void* param) +{ + switch (id) { + case LiveMapModel::EVENT_ID::SPORT_STATUS: { + _view->publish(LiveMapView::MSG_ID::SPORT_STATUS, param); + } break; + + case LiveMapModel::EVENT_ID::GNSS: { + auto gnssInfo = (const HAL::GNSS_Info_t*)param; + MapView::GeoCoord_t geoCoord; + geoCoord.longitude = gnssInfo->longitude; + geoCoord.latitude = gnssInfo->latitude; + geoCoord.altitude = gnssInfo->altitude; + geoCoord.course = gnssInfo->course; + _view->publish(LiveMapView::MSG_ID::GEO_COORD_UPDATE, &geoCoord); + } break; + + case LiveMapModel::EVENT_ID::MAP_LEVEL_CHANGE: + _view->publish(LiveMapView::MSG_ID::MAP_SET_LEVEL, param); + break; + + case LiveMapModel::EVENT_ID::TRACK_REC: + _view->publish(LiveMapView::MSG_ID::TRACK_REC, param); + break; + + default: + break; + } +} + +void LiveMap::onViewEvent(LiveMapView::EVENT_ID id, const void* param) +{ + switch (id) { + case LiveMapView::EVENT_ID::MAP_LEVEL_CHANGED: { + int mapLevel = *(const int*)param; + _model->setMapLevel(mapLevel); + } break; + + default: + break; + } +} diff --git a/x_track/src/App/UI/LiveMap/LiveMap.h b/x_track/src/App/UI/LiveMap/LiveMap.h new file mode 100644 index 0000000000000000000000000000000000000000..5b353f93a91f04038bb5fbbfd5575848456f579a --- /dev/null +++ b/x_track/src/App/UI/LiveMap/LiveMap.h @@ -0,0 +1,57 @@ +/* + * MIT License + * Copyright (c) 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __LIVE_MAP_PRESENTER_H +#define __LIVE_MAP_PRESENTER_H + +#include "LiveMapModel.h" +#include "LiveMapView.h" + +namespace Page { + +class LiveMap : public PageBase, public LiveMapModel::EventListener, public LiveMapView::EventListener { +public: + LiveMap(); + virtual ~LiveMap(); + + virtual void onInstalled(); + virtual void onViewLoad(); + virtual void onViewDidLoad(); + virtual void onViewWillAppear(); + virtual void onViewDidAppear(); + virtual void onViewWillDisappear(); + virtual void onViewDidDisappear(); + virtual void onViewUnload(); + virtual void onViewDidUnload(); + +private: + LiveMapModel* _model; + LiveMapView* _view; + +private: + virtual void onModelEvent(LiveMapModel::EVENT_ID id, const void* param) override; + virtual void onViewEvent(LiveMapView::EVENT_ID id, const void* param) override; +}; + +} + +#endif diff --git a/x_track/src/App/UI/LiveMap/LiveMapModel.cpp b/x_track/src/App/UI/LiveMap/LiveMapModel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9538e13a6de8e8a78fe1c8c4106897e89a80084f --- /dev/null +++ b/x_track/src/App/UI/LiveMap/LiveMapModel.cpp @@ -0,0 +1,139 @@ + +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "LiveMapModel.h" +#include <stdlib.h> + +using namespace Page; + +LiveMapModel::LiveMapModel(EventListener* listener) + : DataNode(__func__, DataProc::broker()) + , _listener(listener) + , _env(this) + , _mapLevel(0) + , _gnssEnable(false) +{ + _nodeGNSS = subscribe("GNSS"); + _nodeSportStatus = subscribe("SportStatus"); + _nodeTrackFilter = subscribe("TrackFilter"); + + subscribe("MapInfo"); + setEventFilter(DataNode::EVENT_PUBLISH); + + auto maplevelStr = _env.get("maplevel"); + if (maplevelStr) { + _mapLevel = atoi(maplevelStr); + } else { + DataProc::Map_Info_t mapInfo; + if (getMapInfo(&mapInfo)) { + _mapLevel = mapInfo.levelMax; + } + } +} + +LiveMapModel::~LiveMapModel() +{ +} + +bool LiveMapModel::getGNSSInfo(HAL::GNSS_Info_t* info) +{ + if (pull(_nodeGNSS, info, sizeof(HAL::GNSS_Info_t)) == DataNode::RES_OK) { + if (info->isVaild) { + return true; + } + } + + memset(info, 0, sizeof(HAL::GNSS_Info_t)); + + /* Use default location */ + DataProc::SportStatus_Info_t ssInfo; + if (pull(_nodeSportStatus, &ssInfo, sizeof(ssInfo)) == DataNode::RES_OK) { + info->longitude = ssInfo.longitude; + info->latitude = ssInfo.latitude; + return true; + } + + return false; +} + +bool LiveMapModel::getMapInfo(DataProc::Map_Info_t* info) +{ + if (_mapInfo.path) { + *info = _mapInfo; + return true; + } + + if (pull("MapInfo", &_mapInfo, sizeof(DataProc::Map_Info_t)) == DataNode::RES_OK) { + *info = _mapInfo; + return true; + } + + return false; +} + +bool LiveMapModel::setMapLevel(int level, bool sendEvent) +{ + DataProc::Map_Info_t mapInfo; + if (!getMapInfo(&mapInfo)) { + return false; + } + + if (level < mapInfo.levelMin || level > mapInfo.levelMax) { + return false; + } + + _mapLevel = level; + if (sendEvent) { + _listener->onModelEvent(EVENT_ID::MAP_LEVEL_CHANGE, &_mapLevel); + } + + return true; +} + +bool LiveMapModel::getTrackFilterInfo(DataProc::TrackFilter_Info_t* info) +{ + return pull(_nodeTrackFilter, info, sizeof(DataProc::TrackFilter_Info_t)) == DataNode::RES_OK; +} + +void LiveMapModel::loadSportStatusInfo() +{ + DataProc::SportStatus_Info_t info; + if (pull(_nodeSportStatus, &info, sizeof(info)) != DataNode::RES_OK) { + return; + } + + _listener->onModelEvent(EVENT_ID::SPORT_STATUS, &info); +} + +int LiveMapModel::onEvent(DataNode::EventParam_t* param) +{ + if (param->tran == _nodeSportStatus) { + _listener->onModelEvent(EVENT_ID::SPORT_STATUS, param->data_p); + } else if (param->tran == _nodeGNSS && _gnssEnable) { + _listener->onModelEvent(EVENT_ID::GNSS, param->data_p); + } else if (param->tran == _nodeTrackFilter) { + _listener->onModelEvent(EVENT_ID::TRACK_REC, param->data_p); + } + + return DataNode::RES_OK; +} diff --git a/x_track/src/App/UI/LiveMap/LiveMapModel.h b/x_track/src/App/UI/LiveMap/LiveMapModel.h new file mode 100644 index 0000000000000000000000000000000000000000..b100361e6de03327d3ee9b662d36198ec6d8825f --- /dev/null +++ b/x_track/src/App/UI/LiveMap/LiveMapModel.h @@ -0,0 +1,78 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __LIVEMAP_MODEL_H +#define __LIVEMAP_MODEL_H + +#include "Service/DataProc/DataProc.h" + +namespace Page { + +class LiveMapModel : private DataNode { +public: + enum class EVENT_ID { + SPORT_STATUS, /* param: SportStatus_Info_t */ + TRACK_REC, /* param: TrackFilter_Info_t */ + GNSS, /* param: GNSS_Info_t */ + MAP_LEVEL_CHANGE, /* param: int */ + _LAST, + }; + + class EventListener { + public: + virtual void onModelEvent(EVENT_ID id, const void* param = nullptr) = 0; + }; + +public: + LiveMapModel(EventListener* listener); + ~LiveMapModel(); + + DataProc::Env_Helper* env() + { + return &_env; + } + + bool getGNSSInfo(HAL::GNSS_Info_t* info); + void setGNSSEnable(bool enable) { _gnssEnable = enable; } + bool getMapInfo(DataProc::Map_Info_t* info); + int getMapLevel() { return _mapLevel; }; + bool setMapLevel(int level, bool sendEvent = false); + bool getTrackFilterInfo(DataProc::TrackFilter_Info_t* info); + void loadSportStatusInfo(); + +private: + EventListener* _listener; + DataProc::Env_Helper _env; + const DataNode* _nodeSportStatus; + const DataNode* _nodeGNSS; + const DataNode* _nodeTrackFilter; + int _mapLevel; + DataProc::Map_Info_t _mapInfo; + bool _gnssEnable; + +private: + virtual int onEvent(DataNode::EventParam_t* param); +}; + +} + +#endif /* __LIVEMAP_MODEL_H */ diff --git a/x_track/src/App/UI/LiveMap/LiveMapView.cpp b/x_track/src/App/UI/LiveMap/LiveMapView.cpp new file mode 100644 index 0000000000000000000000000000000000000000..926c8539610473daa6505024b0c92b2a8d0417cd --- /dev/null +++ b/x_track/src/App/UI/LiveMap/LiveMapView.cpp @@ -0,0 +1,340 @@ +/* + * MIT License + * Copyright (c) 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "LiveMapView.h" +#include "Service/DataProc/DataProc_Def.h" +#include "Utils/TrackView/TrackView.h" + +#define MAP_MOVE_START_STATE LV_STATE_USER_1 +#define SPEED_OVER_STATE LV_STATE_USER_1 + +using namespace Page; + +LiveMapView::LiveMapView(EventListener* listener, lv_obj_t* root) + : _listener(listener) + , _fontSpeed(32, "medium") + , _mapView(nullptr) + , _roadMapView(nullptr) + , _trackView(nullptr) + , _mapConv(true, 16) +{ + lv_obj_set_style_pad_all(root, 0, 0); + + /* label loading */ + { + lv_obj_t* label = lv_label_create(root); + lv_obj_center(label); + lv_label_set_text(label, _("LOADING...")); + + subscribe(MSG_ID::GEO_COORD_UPDATE, label, [](lv_event_t* e) { + auto obj = lv_event_get_target_obj(e); + lv_obj_del(obj); + }); + } + + styleInit(); + mapViewCreate(root); + sportInfoCreate(root); +} + +LiveMapView::~LiveMapView() +{ + delete _roadMapView; + delete _trackView; + delete _mapView; +} + +lv_uintptr_t LiveMapView::msgID(MSG_ID id) +{ + return (lv_uintptr_t)this + (lv_uintptr_t)id; +} + +void LiveMapView::publish(MSG_ID id, const void* payload) +{ + lv_msg_send(msgID(id), payload); +} + +void LiveMapView::subscribe(MSG_ID id, lv_obj_t* obj, lv_event_cb_t event_cb) +{ + lv_msg_subscribe_obj(msgID(id), obj, this); + lv_obj_add_event(obj, event_cb, LV_EVENT_MSG_RECEIVED, this); +} + +void LiveMapView::styleInit() +{ + /* cont style */ + lv_style_set_pad_all(&_style.cont, 0); + lv_style_set_border_width(&_style.cont, 0); + lv_style_set_bg_opa(&_style.cont, LV_OPA_60); + lv_style_set_radius(&_style.cont, 6); + lv_style_set_shadow_width(&_style.cont, 10); + lv_style_set_shadow_color(&_style.cont, lv_color_black()); + + /* line style */ + lv_style_set_line_color(&_style.line, lv_color_hex(0xff931e)); + lv_style_set_line_width(&_style.line, 5); + lv_style_set_line_opa(&_style.line, LV_OPA_COVER); + lv_style_set_line_rounded(&_style.line, true); +} + +void LiveMapView::mapViewCreate(lv_obj_t* par) +{ + /* update view size */ + lv_obj_update_layout(par); + lv_coord_t view_w = lv_obj_get_width(par); + lv_coord_t view_h = lv_obj_get_height(par); + + /* map viewer */ + _mapView = new MapView(view_w, view_h, par, onMapViewEvent, this); + _mapView->setScrollEanble(false); + _mapView->setTileSrcEanble(false); + lv_obj_t* viewCont = _mapView->getViewCont(); + + /* geo coord update */ + subscribe(MSG_ID::GEO_COORD_UPDATE, viewCont, [](lv_event_t* e) { + auto msg = lv_event_get_msg(e); + auto self = (LiveMapView*)lv_event_get_user_data(e); + + if (self->msgID(MSG_ID::GEO_COORD_UPDATE) != lv_msg_get_id(msg)) { + return; + } + + auto geoCoord = (const MapView::GeoCoord_t*)lv_msg_get_payload(msg); + self->_mapView->setGeoCoord(geoCoord); + }); + + /* set map config */ + subscribe(MSG_ID::MAP_CONFIG_UPDATE, viewCont, [](lv_event_t* e) { + auto msg = lv_event_get_msg(e); + auto self = (LiveMapView*)lv_event_get_user_data(e); + + if (self->msgID(MSG_ID::MAP_CONFIG_UPDATE) != lv_msg_get_id(msg)) { + return; + } + + auto mapConfig = (const MapView::MapConfig_t*)lv_msg_get_payload(msg); + self->_mapView->setConfig(mapConfig); + }); + + /* set map level */ + subscribe(MSG_ID::MAP_SET_LEVEL, viewCont, [](lv_event_t* e) { + auto msg = lv_event_get_msg(e); + auto self = (LiveMapView*)lv_event_get_user_data(e); + + if (self->msgID(MSG_ID::MAP_SET_LEVEL) != lv_msg_get_id(msg)) { + return; + } + + auto level = (const int*)lv_msg_get_payload(msg); + self->_mapView->setLevel(*level); + }); + + /* track */ + trackCreate(viewCont); + + /* img arrow */ + _mapView->addArrowImage(ResourcePool::getImage("navi_arrow_light")); +} + +void LiveMapView::onMapViewEvent(MapView::EVENT_ID id, const void* param, void* userData) +{ + auto self = (LiveMapView*)userData; + + switch (id) { + case MapView::EVENT_ID::LEVEL_CHANGED: + self->_listener->onViewEvent(EVENT_ID::MAP_LEVEL_CHANGED, param); + break; + + case MapView::EVENT_ID::TILE_RECT_CHANGED: + self->publish(MSG_ID::MAP_TILE_RECT_CHANGED, param); + break; + + default: + break; + } +} + +void LiveMapView::sportInfoCreate(lv_obj_t* par) +{ + /* cont */ + lv_obj_t* cont = lv_obj_create(par); + lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_add_style(cont, &_style.cont, 0); + lv_obj_set_size(cont, 160, 66); + lv_obj_align(cont, LV_ALIGN_BOTTOM_LEFT, -10, 10); + lv_obj_set_style_radius(cont, 10, 0); + lv_obj_set_style_translate_y(cont, 180, MAP_MOVE_START_STATE); + + static const lv_style_prop_t prop[] = { + LV_STYLE_TRANSLATE_Y, + LV_STYLE_PROP_INV + }; + static lv_style_transition_dsc_t tran; + lv_style_transition_dsc_init(&tran, prop, lv_anim_path_ease_out, 300, 0, nullptr); + lv_obj_set_style_transition(cont, &tran, LV_STATE_DEFAULT); + + /* speed */ + lv_obj_t* labelSpeed = lv_label_create(cont); + { + lv_label_set_text(labelSpeed, "00"); + lv_obj_set_style_text_font(labelSpeed, _fontSpeed, 0); + lv_obj_align(labelSpeed, LV_ALIGN_LEFT_MID, 20, -10); + lv_obj_set_style_translate_x(labelSpeed, -8, SPEED_OVER_STATE); + + subscribe(MSG_ID::SPORT_STATUS, labelSpeed, [](lv_event_t* e) { + auto obj = lv_event_get_current_target_obj(e); + auto msg = lv_event_get_msg(e); + auto info = (const DataProc::SportStatus_Info_t*)lv_msg_get_payload(msg); + auto speed = (int)info->speedKph; + lv_label_set_text_fmt(obj, "%02d", speed); + + if (speed >= 100) { + lv_obj_add_state(obj, SPEED_OVER_STATE); + } else { + lv_obj_clear_state(obj, SPEED_OVER_STATE); + } + }); + } + + /* unit */ + { + lv_obj_t* labelUnit = lv_label_create(cont); + lv_label_set_text(labelUnit, "km/h"); + lv_obj_set_style_text_font(labelUnit, lv_theme_get_font_small(labelUnit), 0); + lv_obj_align_to(labelUnit, labelSpeed, LV_ALIGN_OUT_BOTTOM_MID, 0, -8); + } + + /* trip dist */ + { + lv_obj_t* label = lv_label_create(cont); + lv_label_set_text(label, LV_SYMBOL_EXT_ROAD " --"); + lv_obj_align(label, LV_ALIGN_TOP_LEFT, 73, 10); + + subscribe(MSG_ID::SPORT_STATUS, label, [](lv_event_t* e) { + auto obj = lv_event_get_current_target_obj(e); + auto msg = lv_event_get_msg(e); + auto info = (const DataProc::SportStatus_Info_t*)lv_msg_get_payload(msg); + auto dist = info->singleDistance / 1000.0f; + if (dist < 100) { + lv_label_set_text_fmt(obj, LV_SYMBOL_EXT_ROAD " %0.1f km", dist); + } else { + lv_label_set_text_fmt(obj, LV_SYMBOL_EXT_ROAD " %d km", (int)dist); + } + }); + } + + /* trip time */ + { + lv_obj_t* label = lv_label_create(cont); + lv_label_set_text(label, LV_SYMBOL_EXT_CLOCK "--"); + lv_obj_align(label, LV_ALIGN_TOP_LEFT, 74, 30); + + subscribe(MSG_ID::SPORT_STATUS, label, [](lv_event_t* e) { + auto obj = lv_event_get_target_obj(e); + auto msg = lv_event_get_msg(e); + auto info = (const DataProc::SportStatus_Info_t*)lv_msg_get_payload(msg); + + uint64_t ss = info->singleTime / 1000; + uint64_t mm = ss / 60; + uint32_t hh = (uint32_t)(mm / 60); + + lv_label_set_text_fmt(obj, + LV_SYMBOL_EXT_CLOCK " %d:%02d:%02d", + (int)hh, + (int)(mm % 60), + (int)(ss % 60)); + }); + } +} + +void LiveMapView::trackCreate(lv_obj_t* par) +{ + _trackView = new TrackView(par); + _trackView->setStyle(&_style.line); + + lv_obj_t* cont = _trackView->getViewCont(); + + /* track recorder state change */ + subscribe(MSG_ID::TRACK_REC, cont, [](lv_event_t* e) { + auto msg = lv_event_get_msg(e); + auto self = (LiveMapView*)lv_event_get_user_data(e); + + if (self->msgID(MSG_ID::TRACK_REC) != lv_msg_get_id(msg)) { + return; + } + + auto info = (const DataProc::TrackFilter_Info_t*)lv_msg_get_payload(msg); + self->_trackView->setPointContainer((PointContainer*)info->pointCont, info->level); + self->_trackView->reload(); + }); + + /* map tile change */ + subscribe(MSG_ID::MAP_TILE_RECT_CHANGED, cont, [](lv_event_t* e) { + auto msg = lv_event_get_msg(e); + auto self = (LiveMapView*)lv_event_get_user_data(e); + + if (self->msgID(MSG_ID::MAP_TILE_RECT_CHANGED) != lv_msg_get_id(msg)) { + return; + } + + auto info = (const TileView::Rect_t*)lv_msg_get_payload(msg); + self->_trackView->setArea(info->x, info->y, info->x + info->width - 1, info->y + info->height - 1); + self->_trackView->reload(); + }); + + /* map level change */ + subscribe(MSG_ID::MAP_SET_LEVEL, cont, [](lv_event_t* e) { + auto msg = lv_event_get_msg(e); + auto self = (LiveMapView*)lv_event_get_user_data(e); + + if (self->msgID(MSG_ID::MAP_SET_LEVEL) != lv_msg_get_id(msg)) { + return; + } + + auto level = *(const int*)lv_msg_get_payload(msg); + self->_trackView->setLevel(level); + self->_trackView->reload(); + }); + + /* geo coord update */ + subscribe(MSG_ID::GEO_COORD_UPDATE, cont, [](lv_event_t* e) { + auto msg = lv_event_get_msg(e); + auto self = (LiveMapView*)lv_event_get_user_data(e); + + if (self->msgID(MSG_ID::GEO_COORD_UPDATE) != lv_msg_get_id(msg)) { + return; + } + + auto geoCoord = (const MapView::GeoCoord_t*)lv_msg_get_payload(msg); + + /* sync status */ + self->_mapConv.setCoordTransformEnable(self->_mapView->getCoordTrans()); + self->_mapConv.setLevel(self->_trackView->getRefLevel()); + + /* convert coord */ + MapConv::Point_t point = self->_mapConv.getCoordinate(geoCoord->longitude, geoCoord->latitude); + + /* push point */ + self->_trackView->pushPoint(point.x, point.y); + self->_trackView->setActivePoint(point.x, point.y); + }); +} diff --git a/x_track/src/App/UI/LiveMap/LiveMapView.h b/x_track/src/App/UI/LiveMap/LiveMapView.h new file mode 100644 index 0000000000000000000000000000000000000000..48a16c2f115594673d46a216f5e3d9f9dcad4161 --- /dev/null +++ b/x_track/src/App/UI/LiveMap/LiveMapView.h @@ -0,0 +1,97 @@ +/* + * MIT License + * Copyright (c) 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __LIVE_MAP_VIEW_H +#define __LIVE_MAP_VIEW_H + +#include "../Page.h" +#include "Utils/MapView/MapView.h" + +class TrackView; + +namespace Page { + +class LiveMapView { +public: + enum class EVENT_ID { + MAP_LEVEL_CHANGED, /* param: None */ + LOAD_FILE, /* param: None */ + _LAST, + }; + + enum class MSG_ID { + GEO_COORD_UPDATE, /* param: MapView::GeoCoord_t */ + SPORT_STATUS, /* param: SportStatus_Infot */ + TRACK_REC, /* param: DataProc::TrackFilter_Info_t */ + MAP_CONFIG_UPDATE, /* param: MapView::MapConfig_t */ + MAP_SET_LEVEL, /* param: int */ + MAP_TILE_RECT_CHANGED, /* param: TileView::TileRect_t */ + LOAD_ROAD_MAP, /* param: DataProc::RoadMap_Info_t */ + _LAST, + }; + + class EventListener { + public: + virtual void onViewEvent(EVENT_ID id, const void* param = nullptr) = 0; + }; + +public: + LiveMapView(EventListener* listener, lv_obj_t* root); + ~LiveMapView(); + void publish(MSG_ID id, const void* payload = nullptr); + +private: + EventListener* _listener; + ResourcePool::Font _fontSpeed; + struct STYLE { + STYLE() + { + lv_style_init(&cont); + lv_style_init(&line); + } + ~STYLE() + { + lv_style_reset(&cont); + lv_style_reset(&line); + } + lv_style_t cont; + lv_style_t line; + } _style; + + MapView* _mapView; + TrackView* _roadMapView; + TrackView* _trackView; + MapConv _mapConv; + +private: + lv_uintptr_t msgID(MSG_ID id); + void subscribe(MSG_ID id, lv_obj_t* obj, lv_event_cb_t event_cb); + void styleInit(); + void mapViewCreate(lv_obj_t* par); + static void onMapViewEvent(MapView::EVENT_ID id, const void* param, void* userData); + void sportInfoCreate(lv_obj_t* par); + void trackCreate(lv_obj_t* par); +}; + +} + +#endif /* __LIVE_MAP_VIEW_H */ diff --git a/x_track/src/App/UI/Page.h b/x_track/src/App/UI/Page.h new file mode 100644 index 0000000000000000000000000000000000000000..ab73d9b08b18722d88fdf4ac7e054328f6ecc90e --- /dev/null +++ b/x_track/src/App/UI/Page.h @@ -0,0 +1,32 @@ +/* + * MIT License + * Copyright (c) 2021 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __PAGE_H +#define __PAGE_H + +#include "AppFactory.h" +#include "Frameworks/PageManager/PageManager.h" +#include "Resource/ResourcePool.h" +#include "Service/i18n/lv_i18n.h" +#include "Utils/lv_msg/lv_msg.h" + +#endif diff --git a/x_track/src/App/UI/Resource/ResourcePool.cpp b/x_track/src/App/UI/Resource/ResourcePool.cpp new file mode 100755 index 0000000000000000000000000000000000000000..e4fbe5eb51ed6d0414ce2cd926f2e546f2018f4a --- /dev/null +++ b/x_track/src/App/UI/Resource/ResourcePool.cpp @@ -0,0 +1,155 @@ +/* + * MIT License + * Copyright (c) 2022 XCLZ STUDIO@W-Mai + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "ResourcePool.h" +#include "Config/Config.h" +#include "Frameworks/ResourceManager/ResourceManagerStatic.cpp" +#include "ResourcePoolDefs.h" +#include "Utils/lv_msg/lv_msg.h" +#include <uikit/uikit.h> +#include <cstdlib> + +/* clang-format off */ + +#define FONT_REGULAR_NAME "Alibaba_PuHuiTi_2.0_55_Regular" +#define FONT_MEDIUM_NAME "Alibaba_PuHuiTi_2.0_65_Medium" +#define FONT_BOLD_NAME "Alibaba_PuHuiTi_2.0_115_Black" +#define FONT_AWESOME_BRANDS_REGULAR_NAME "Font Awesome 6 Brands-Regular-400" +#define FONT_AWESOME_FREE_REGULAR_NAME "Font Awesome 6 Free-Regular-400" +#define FONT_AWESOME_FREE_SOLID_NAME "Font Awesome 6 Free-Solid-900" + +/* clang-format on */ + +namespace ResourcePool { + +DEF_RES_MNGR_OBJ_EXT(Font, font_value_t, + IMPORT_FONT_NATIVE(montserrat, 14), + IMPORT_FONT_FILE("regular", FONT_REGULAR_NAME), + IMPORT_FONT_FILE("medium", FONT_MEDIUM_NAME), + IMPORT_FONT_FILE("bold", FONT_BOLD_NAME), + IMPORT_FONT_FILE("awesome", FONT_AWESOME_FREE_SOLID_NAME), ); + +DEF_RES_MNGR_OBJ(Image, + IMPORT_IMAGE_FILE("navi_arrow_dark"), + IMPORT_IMAGE_FILE("navi_arrow_light"), ); + +void init() +{ + lv_msg_init(); + vg_init(); + +#define FONT_MANAGER_ADD_FONT(name) vg_font_add_path(name, FONT_MAKE_PATH(name)); + + FONT_MANAGER_ADD_FONT(FONT_REGULAR_NAME); + FONT_MANAGER_ADD_FONT(FONT_MEDIUM_NAME); + FONT_MANAGER_ADD_FONT(FONT_BOLD_NAME); + FONT_MANAGER_ADD_FONT(FONT_AWESOME_BRANDS_REGULAR_NAME); + FONT_MANAGER_ADD_FONT(FONT_AWESOME_FREE_REGULAR_NAME); + FONT_MANAGER_ADD_FONT(FONT_AWESOME_FREE_SOLID_NAME); + + font_value_t default_font = { + font_value_type::UNKNOWN, + LV_FONT_DEFAULT + }; + SET_DEFAULT(Font, std::forward<font_value_t>(default_font)); + SET_DEFAULT(Image, LV_SYMBOL_WARNING); +} + +void deinit() +{ +} + +/** + * @brief Parse real font name and font size from input name. + * @param name Font name + * @param size Return font size + * @return Pointer of real NAME start + */ +static const char* get_font_size_from_name(const char* name, int* size) +{ + if (name[0] != '<' || size == nullptr) { + return name; + } + LV_ASSERT_NULL(size); + + /* +1 to skip '<' */ + char* end_ptr; + *size = (int)strtol(name + 1, &end_ptr, 10); + if (end_ptr == name) { + return name; + } + return end_ptr + 1; /* +1 to skip '>' */ +} + +DEF_RES_MNGR_GET(Font) +{ + /* font key: "name_size" */ + + auto font = OName(Font).get(key); + + /* native font */ + if (font.type == font_value_type::NATIVE) { + return font.value; + } + + int size = -1; + auto font_key = get_font_size_from_name(key, &size); + auto freetype_font = OName(Font).get(font_key); + + if (freetype_font.type == font_value_type::UNKNOWN) { + LV_LOG_ERROR("error key = %s", key); + return font.value; + } + + if (size <= 0) { + LV_LOG_ERROR("error size = %d", size); + return font.value; + } + + const lv_font_t* new_font = vg_font_create( + (const char*)freetype_font.value, + size, + LV_FREETYPE_FONT_STYLE_NORMAL); + + if (!new_font) { + new_font = LV_FONT_DEFAULT; + LV_LOG_WARN("new_font is NULL, use LV_FONT_DEFAULT"); + } + + return new_font; +} + +void dropFont(const lv_font_t* font) +{ + if (font == LV_FONT_DEFAULT) { + return; + } + + vg_font_destroy((lv_font_t*)font); +} + +DEF_RES_MNGR_GET(Image) +{ + return OName(Image).get(key); +} +} diff --git a/x_track/src/App/UI/Resource/ResourcePool.h b/x_track/src/App/UI/Resource/ResourcePool.h new file mode 100755 index 0000000000000000000000000000000000000000..96d1b9b7bc9d5677ca68e64f5b19f75bd60fb7ec --- /dev/null +++ b/x_track/src/App/UI/Resource/ResourcePool.h @@ -0,0 +1,78 @@ +/* + * MIT License + * Copyright (c) 2022 XCLZ STUDIO@W-Mai + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef RESOURCE_POOL_H +#define RESOURCE_POOL_H + +#include "lv_symbol_ext_def.h" +#include "lvgl/lvgl.h" + +/* Macro Definitions */ +#define RES_MNG(NAME, K_TYPE, V_TYPE) \ + using KType_##NAME = K_TYPE; \ + using VType_##NAME = V_TYPE; \ + VType_##NAME get##NAME(KType_##NAME key) + +namespace ResourcePool { + +void init(); +void deinit(); + +RES_MNG(Font, const char*, const lv_font_t*); +RES_MNG(Image, const char*, const void*); + +void dropFont(const lv_font_t* font); + +/* Font Wrapper */ +class Font { +public: + Font(const char* key) + { + _font = getFont(key); + } + Font(int size, const char* name) + { + char key[64]; + lv_snprintf(key, sizeof(key), "<%d>%s", size, name); + _font = getFont(key); + } + ~Font() + { + dropFont(_font); + } + + operator const lv_font_t*() const + { + return _font; + } + +private: + const lv_font_t* _font; +}; + +} + +/* Macro UnDefinitions */ +#undef RES_MNG + +#endif // RESOURCE_POOL_H diff --git a/x_track/src/App/UI/Resource/ResourcePoolDefs.h b/x_track/src/App/UI/Resource/ResourcePoolDefs.h new file mode 100755 index 0000000000000000000000000000000000000000..68112518485513fbe114810411f269be5a327825 --- /dev/null +++ b/x_track/src/App/UI/Resource/ResourcePoolDefs.h @@ -0,0 +1,90 @@ +/* + * MIT License + * Copyright (c) 2022 XCLZ STUDIO@W-Mai + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Created by W-Mai on 2022/11/23. + */ + +#ifndef SIMULATOR_RESOURCEPOOLDEFS_H +#define SIMULATOR_RESOURCEPOOLDEFS_H + +#include "Config/Config.h" + +#define KType(NAME) KType_##NAME +#define VType(NAME) VType_##NAME +#define OName(NAME) __RMSO_##NAME##_ +#define ONameArray(NAME) __RMSO_##NAME##_ARRAY +#define RMSType(NAME) RMSType_##NAME + +#define DEF_RES_MNGR_OBJ_EXT(NAME, VTYPE, ...) \ + static std::pair<KType(NAME), VTYPE> ONameArray(NAME)[] { \ + __VA_ARGS__ \ + }; \ + using RMSType(NAME) = ResourceManagerStatic<KType(NAME), VTYPE, \ + sizeof(ONameArray(NAME)) / sizeof(std::pair<KType(NAME), VTYPE>)>; \ + static RMSType(NAME) OName(NAME)(ONameArray(NAME)) + +#define DEF_RES_MNGR_OBJ(NAME, ...) DEF_RES_MNGR_OBJ_EXT(NAME, VType(NAME), __VA_ARGS__) + +#define DEF_RES_MNGR_GET(NAME) VType(NAME) get##NAME(KType(NAME) key) + +#define IMPORT_FONT_NATIVE(NAME, SIZE) \ + { \ + "<" #SIZE ">" #NAME, \ + { \ + font_value_type::NATIVE, \ + &lv_font_##NAME##_##SIZE \ + } \ + } + +#define IMPORT_FONT_FILE(NAME, FONT_NAME) \ + { \ + NAME, \ + { \ + font_value_type::FREETYPE, \ + (lv_font_t*)FONT_NAME \ + } \ + } + +#define IMPORT_IMAGE_NATIVE(NAME) \ + { \ +#NAME, &img_src_##NAME \ + } + +#define IMPORT_IMAGE_FILE(NAME) \ + { \ + NAME, (CONFIG_IMAGE_DIR_PATH "/" NAME CONFIG_IMAGE_EXT_NAME) \ + } + +#define SET_DEFAULT(NAME, VALUE) OName(NAME).setDefaultValue((VALUE)) + +enum font_value_type { + UNKNOWN, + NATIVE, + FREETYPE +}; + +struct font_value_t { + font_value_type type; + const lv_font_t* value; +}; + +#endif // SIMULATOR_RESOURCEPOOLDEFS_H diff --git a/x_track/src/App/UI/Resource/lv_symbol_ext_def.h b/x_track/src/App/UI/Resource/lv_symbol_ext_def.h new file mode 100755 index 0000000000000000000000000000000000000000..becf736f30418ebf28fec78b4f37eb08448706fa --- /dev/null +++ b/x_track/src/App/UI/Resource/lv_symbol_ext_def.h @@ -0,0 +1,1883 @@ +/* + * This file is automatically generated by a script. + * Please DO NOT manually edit this file. + */ + +#ifndef LV_SYMBOL_EXT_DEF +#define LV_SYMBOL_EXT_DEF + +#define LV_SYMBOL_EXT_0 "\x30" /*48, 30*/ +#define LV_SYMBOL_EXT_1 "\x31" /*49, 31*/ +#define LV_SYMBOL_EXT_2 "\x32" /*50, 32*/ +#define LV_SYMBOL_EXT_3 "\x33" /*51, 33*/ +#define LV_SYMBOL_EXT_4 "\x34" /*52, 34*/ +#define LV_SYMBOL_EXT_5 "\x35" /*53, 35*/ +#define LV_SYMBOL_EXT_6 "\x36" /*54, 36*/ +#define LV_SYMBOL_EXT_7 "\x37" /*55, 37*/ +#define LV_SYMBOL_EXT_8 "\x38" /*56, 38*/ +#define LV_SYMBOL_EXT_9 "\x39" /*57, 39*/ +#define LV_SYMBOL_EXT_42_GROUP "\xEE\x82\x80" /*57472, e080*/ +#define LV_SYMBOL_EXT_500PX "\xEF\x89\xAE" /*62062, f26e*/ +#define LV_SYMBOL_EXT_A "\x41" /*65, 41*/ +#define LV_SYMBOL_EXT_ACCESSIBLE_ICON "\xEF\x8D\xA8" /*62312, f368*/ +#define LV_SYMBOL_EXT_ACCUSOFT "\xEF\x8D\xA9" /*62313, f369*/ +#define LV_SYMBOL_EXT_ADDRESS_BOOK "\xEF\x8A\xB9" /*62137, f2b9*/ +#define LV_SYMBOL_EXT_ADDRESS_CARD "\xEF\x8A\xBB" /*62139, f2bb*/ +#define LV_SYMBOL_EXT_ADN "\xEF\x85\xB0" /*61808, f170*/ +#define LV_SYMBOL_EXT_ADVERSAL "\xEF\x8D\xAA" /*62314, f36a*/ +#define LV_SYMBOL_EXT_AFFILIATETHEME "\xEF\x8D\xAB" /*62315, f36b*/ +#define LV_SYMBOL_EXT_AIRBNB "\xEF\xA0\xB4" /*63540, f834*/ +#define LV_SYMBOL_EXT_ALGOLIA "\xEF\x8D\xAC" /*62316, f36c*/ +#define LV_SYMBOL_EXT_ALIGN_CENTER "\xEF\x80\xB7" /*61495, f037*/ +#define LV_SYMBOL_EXT_ALIGN_JUSTIFY "\xEF\x80\xB9" /*61497, f039*/ +#define LV_SYMBOL_EXT_ALIGN_LEFT "\xEF\x80\xB6" /*61494, f036*/ +#define LV_SYMBOL_EXT_ALIGN_RIGHT "\xEF\x80\xB8" /*61496, f038*/ +#define LV_SYMBOL_EXT_ALIPAY "\xEF\x99\x82" /*63042, f642*/ +#define LV_SYMBOL_EXT_AMAZON "\xEF\x89\xB0" /*62064, f270*/ +#define LV_SYMBOL_EXT_AMAZON_PAY "\xEF\x90\xAC" /*62508, f42c*/ +#define LV_SYMBOL_EXT_AMILIA "\xEF\x8D\xAD" /*62317, f36d*/ +#define LV_SYMBOL_EXT_ANCHOR "\xEF\x84\xBD" /*61757, f13d*/ +#define LV_SYMBOL_EXT_ANCHOR_CIRCLE_CHECK "\xEE\x92\xAA" /*58538, e4aa*/ +#define LV_SYMBOL_EXT_ANCHOR_CIRCLE_EXCLAMATION "\xEE\x92\xAB" /*58539, e4ab*/ +#define LV_SYMBOL_EXT_ANCHOR_CIRCLE_XMARK "\xEE\x92\xAC" /*58540, e4ac*/ +#define LV_SYMBOL_EXT_ANCHOR_LOCK "\xEE\x92\xAD" /*58541, e4ad*/ +#define LV_SYMBOL_EXT_ANDROID "\xEF\x85\xBB" /*61819, f17b*/ +#define LV_SYMBOL_EXT_ANGELLIST "\xEF\x88\x89" /*61961, f209*/ +#define LV_SYMBOL_EXT_ANGLE_DOWN "\xEF\x84\x87" /*61703, f107*/ +#define LV_SYMBOL_EXT_ANGLE_LEFT "\xEF\x84\x84" /*61700, f104*/ +#define LV_SYMBOL_EXT_ANGLE_RIGHT "\xEF\x84\x85" /*61701, f105*/ +#define LV_SYMBOL_EXT_ANGLE_UP "\xEF\x84\x86" /*61702, f106*/ +#define LV_SYMBOL_EXT_ANGLES_DOWN "\xEF\x84\x83" /*61699, f103*/ +#define LV_SYMBOL_EXT_ANGLES_LEFT "\xEF\x84\x80" /*61696, f100*/ +#define LV_SYMBOL_EXT_ANGLES_RIGHT "\xEF\x84\x81" /*61697, f101*/ +#define LV_SYMBOL_EXT_ANGLES_UP "\xEF\x84\x82" /*61698, f102*/ +#define LV_SYMBOL_EXT_ANGRYCREATIVE "\xEF\x8D\xAE" /*62318, f36e*/ +#define LV_SYMBOL_EXT_ANGULAR "\xEF\x90\xA0" /*62496, f420*/ +#define LV_SYMBOL_EXT_ANKH "\xEF\x99\x84" /*63044, f644*/ +#define LV_SYMBOL_EXT_APP_STORE "\xEF\x8D\xAF" /*62319, f36f*/ +#define LV_SYMBOL_EXT_APP_STORE_IOS "\xEF\x8D\xB0" /*62320, f370*/ +#define LV_SYMBOL_EXT_APPER "\xEF\x8D\xB1" /*62321, f371*/ +#define LV_SYMBOL_EXT_APPLE "\xEF\x85\xB9" /*61817, f179*/ +#define LV_SYMBOL_EXT_APPLE_PAY "\xEF\x90\x95" /*62485, f415*/ +#define LV_SYMBOL_EXT_APPLE_WHOLE "\xEF\x97\x91" /*62929, f5d1*/ +#define LV_SYMBOL_EXT_ARCHWAY "\xEF\x95\x97" /*62807, f557*/ +#define LV_SYMBOL_EXT_ARROW_DOWN "\xEF\x81\xA3" /*61539, f063*/ +#define LV_SYMBOL_EXT_ARROW_DOWN_1_9 "\xEF\x85\xA2" /*61794, f162*/ +#define LV_SYMBOL_EXT_ARROW_DOWN_9_1 "\xEF\xA2\x86" /*63622, f886*/ +#define LV_SYMBOL_EXT_ARROW_DOWN_A_Z "\xEF\x85\x9D" /*61789, f15d*/ +#define LV_SYMBOL_EXT_ARROW_DOWN_LONG "\xEF\x85\xB5" /*61813, f175*/ +#define LV_SYMBOL_EXT_ARROW_DOWN_SHORT_WIDE "\xEF\xA2\x84" /*63620, f884*/ +#define LV_SYMBOL_EXT_ARROW_DOWN_UP_ACROSS_LINE "\xEE\x92\xAF" /*58543, e4af*/ +#define LV_SYMBOL_EXT_ARROW_DOWN_UP_LOCK "\xEE\x92\xB0" /*58544, e4b0*/ +#define LV_SYMBOL_EXT_ARROW_DOWN_WIDE_SHORT "\xEF\x85\xA0" /*61792, f160*/ +#define LV_SYMBOL_EXT_ARROW_DOWN_Z_A "\xEF\xA2\x81" /*63617, f881*/ +#define LV_SYMBOL_EXT_ARROW_LEFT "\xEF\x81\xA0" /*61536, f060*/ +#define LV_SYMBOL_EXT_ARROW_LEFT_LONG "\xEF\x85\xB7" /*61815, f177*/ +#define LV_SYMBOL_EXT_ARROW_POINTER "\xEF\x89\x85" /*62021, f245*/ +#define LV_SYMBOL_EXT_ARROW_RIGHT "\xEF\x81\xA1" /*61537, f061*/ +#define LV_SYMBOL_EXT_ARROW_RIGHT_ARROW_LEFT "\xEF\x83\xAC" /*61676, f0ec*/ +#define LV_SYMBOL_EXT_ARROW_RIGHT_FROM_BRACKET "\xEF\x82\x8B" /*61579, f08b*/ +#define LV_SYMBOL_EXT_ARROW_RIGHT_LONG "\xEF\x85\xB8" /*61816, f178*/ +#define LV_SYMBOL_EXT_ARROW_RIGHT_TO_BRACKET "\xEF\x82\x90" /*61584, f090*/ +#define LV_SYMBOL_EXT_ARROW_RIGHT_TO_CITY "\xEE\x92\xB3" /*58547, e4b3*/ +#define LV_SYMBOL_EXT_ARROW_ROTATE_LEFT "\xEF\x83\xA2" /*61666, f0e2*/ +#define LV_SYMBOL_EXT_ARROW_ROTATE_RIGHT "\xEF\x80\x9E" /*61470, f01e*/ +#define LV_SYMBOL_EXT_ARROW_TREND_DOWN "\xEE\x82\x97" /*57495, e097*/ +#define LV_SYMBOL_EXT_ARROW_TREND_UP "\xEE\x82\x98" /*57496, e098*/ +#define LV_SYMBOL_EXT_ARROW_TURN_DOWN "\xEF\x85\x89" /*61769, f149*/ +#define LV_SYMBOL_EXT_ARROW_TURN_UP "\xEF\x85\x88" /*61768, f148*/ +#define LV_SYMBOL_EXT_ARROW_UP "\xEF\x81\xA2" /*61538, f062*/ +#define LV_SYMBOL_EXT_ARROW_UP_1_9 "\xEF\x85\xA3" /*61795, f163*/ +#define LV_SYMBOL_EXT_ARROW_UP_9_1 "\xEF\xA2\x87" /*63623, f887*/ +#define LV_SYMBOL_EXT_ARROW_UP_A_Z "\xEF\x85\x9E" /*61790, f15e*/ +#define LV_SYMBOL_EXT_ARROW_UP_FROM_BRACKET "\xEE\x82\x9A" /*57498, e09a*/ +#define LV_SYMBOL_EXT_ARROW_UP_FROM_GROUND_WATER "\xEE\x92\xB5" /*58549, e4b5*/ +#define LV_SYMBOL_EXT_ARROW_UP_FROM_WATER_PUMP "\xEE\x92\xB6" /*58550, e4b6*/ +#define LV_SYMBOL_EXT_ARROW_UP_LONG "\xEF\x85\xB6" /*61814, f176*/ +#define LV_SYMBOL_EXT_ARROW_UP_RIGHT_DOTS "\xEE\x92\xB7" /*58551, e4b7*/ +#define LV_SYMBOL_EXT_ARROW_UP_RIGHT_FROM_SQUARE "\xEF\x82\x8E" /*61582, f08e*/ +#define LV_SYMBOL_EXT_ARROW_UP_SHORT_WIDE "\xEF\xA2\x85" /*63621, f885*/ +#define LV_SYMBOL_EXT_ARROW_UP_WIDE_SHORT "\xEF\x85\xA1" /*61793, f161*/ +#define LV_SYMBOL_EXT_ARROW_UP_Z_A "\xEF\xA2\x82" /*63618, f882*/ +#define LV_SYMBOL_EXT_ARROWS_DOWN_TO_LINE "\xEE\x92\xB8" /*58552, e4b8*/ +#define LV_SYMBOL_EXT_ARROWS_DOWN_TO_PEOPLE "\xEE\x92\xB9" /*58553, e4b9*/ +#define LV_SYMBOL_EXT_ARROWS_LEFT_RIGHT "\xEF\x81\xBE" /*61566, f07e*/ +#define LV_SYMBOL_EXT_ARROWS_LEFT_RIGHT_TO_LINE "\xEE\x92\xBA" /*58554, e4ba*/ +#define LV_SYMBOL_EXT_ARROWS_ROTATE "\xEF\x80\xA1" /*61473, f021*/ +#define LV_SYMBOL_EXT_ARROWS_SPIN "\xEE\x92\xBB" /*58555, e4bb*/ +#define LV_SYMBOL_EXT_ARROWS_SPLIT_UP_AND_LEFT "\xEE\x92\xBC" /*58556, e4bc*/ +#define LV_SYMBOL_EXT_ARROWS_TO_CIRCLE "\xEE\x92\xBD" /*58557, e4bd*/ +#define LV_SYMBOL_EXT_ARROWS_TO_DOT "\xEE\x92\xBE" /*58558, e4be*/ +#define LV_SYMBOL_EXT_ARROWS_TO_EYE "\xEE\x92\xBF" /*58559, e4bf*/ +#define LV_SYMBOL_EXT_ARROWS_TURN_RIGHT "\xEE\x93\x80" /*58560, e4c0*/ +#define LV_SYMBOL_EXT_ARROWS_TURN_TO_DOTS "\xEE\x93\x81" /*58561, e4c1*/ +#define LV_SYMBOL_EXT_ARROWS_UP_DOWN "\xEF\x81\xBD" /*61565, f07d*/ +#define LV_SYMBOL_EXT_ARROWS_UP_DOWN_LEFT_RIGHT "\xEF\x81\x87" /*61511, f047*/ +#define LV_SYMBOL_EXT_ARROWS_UP_TO_LINE "\xEE\x93\x82" /*58562, e4c2*/ +#define LV_SYMBOL_EXT_ARTSTATION "\xEF\x9D\xBA" /*63354, f77a*/ +#define LV_SYMBOL_EXT_ASTERISK "\x2A" /*42, 2a*/ +#define LV_SYMBOL_EXT_ASYMMETRIK "\xEF\x8D\xB2" /*62322, f372*/ +#define LV_SYMBOL_EXT_AT "\x40" /*64, 40*/ +#define LV_SYMBOL_EXT_ATLASSIAN "\xEF\x9D\xBB" /*63355, f77b*/ +#define LV_SYMBOL_EXT_ATOM "\xEF\x97\x92" /*62930, f5d2*/ +#define LV_SYMBOL_EXT_AUDIBLE "\xEF\x8D\xB3" /*62323, f373*/ +#define LV_SYMBOL_EXT_AUDIO_DESCRIPTION "\xEF\x8A\x9E" /*62110, f29e*/ +#define LV_SYMBOL_EXT_AUSTRAL_SIGN "\xEE\x82\xA9" /*57513, e0a9*/ +#define LV_SYMBOL_EXT_AUTOPREFIXER "\xEF\x90\x9C" /*62492, f41c*/ +#define LV_SYMBOL_EXT_AVIANEX "\xEF\x8D\xB4" /*62324, f374*/ +#define LV_SYMBOL_EXT_AVIATO "\xEF\x90\xA1" /*62497, f421*/ +#define LV_SYMBOL_EXT_AWARD "\xEF\x95\x99" /*62809, f559*/ +#define LV_SYMBOL_EXT_AWS "\xEF\x8D\xB5" /*62325, f375*/ +#define LV_SYMBOL_EXT_B "\x42" /*66, 42*/ +#define LV_SYMBOL_EXT_BABY "\xEF\x9D\xBC" /*63356, f77c*/ +#define LV_SYMBOL_EXT_BABY_CARRIAGE "\xEF\x9D\xBD" /*63357, f77d*/ +#define LV_SYMBOL_EXT_BACKWARD "\xEF\x81\x8A" /*61514, f04a*/ +#define LV_SYMBOL_EXT_BACKWARD_FAST "\xEF\x81\x89" /*61513, f049*/ +#define LV_SYMBOL_EXT_BACKWARD_STEP "\xEF\x81\x88" /*61512, f048*/ +#define LV_SYMBOL_EXT_BACON "\xEF\x9F\xA5" /*63461, f7e5*/ +#define LV_SYMBOL_EXT_BACTERIA "\xEE\x81\x99" /*57433, e059*/ +#define LV_SYMBOL_EXT_BACTERIUM "\xEE\x81\x9A" /*57434, e05a*/ +#define LV_SYMBOL_EXT_BAG_SHOPPING "\xEF\x8A\x90" /*62096, f290*/ +#define LV_SYMBOL_EXT_BAHAI "\xEF\x99\xA6" /*63078, f666*/ +#define LV_SYMBOL_EXT_BAHT_SIGN "\xEE\x82\xAC" /*57516, e0ac*/ +#define LV_SYMBOL_EXT_BAN "\xEF\x81\x9E" /*61534, f05e*/ +#define LV_SYMBOL_EXT_BAN_SMOKING "\xEF\x95\x8D" /*62797, f54d*/ +#define LV_SYMBOL_EXT_BANDAGE "\xEF\x91\xA2" /*62562, f462*/ +#define LV_SYMBOL_EXT_BANDCAMP "\xEF\x8B\x95" /*62165, f2d5*/ +#define LV_SYMBOL_EXT_BANGLADESHI_TAKA_SIGN "\xEE\x8B\xA6" /*58086, e2e6*/ +#define LV_SYMBOL_EXT_BARCODE "\xEF\x80\xAA" /*61482, f02a*/ +#define LV_SYMBOL_EXT_BARS "\xEF\x83\x89" /*61641, f0c9*/ +#define LV_SYMBOL_EXT_BARS_PROGRESS "\xEF\xA0\xA8" /*63528, f828*/ +#define LV_SYMBOL_EXT_BARS_STAGGERED "\xEF\x95\x90" /*62800, f550*/ +#define LV_SYMBOL_EXT_BASEBALL "\xEF\x90\xB3" /*62515, f433*/ +#define LV_SYMBOL_EXT_BASEBALL_BAT_BALL "\xEF\x90\xB2" /*62514, f432*/ +#define LV_SYMBOL_EXT_BASKET_SHOPPING "\xEF\x8A\x91" /*62097, f291*/ +#define LV_SYMBOL_EXT_BASKETBALL "\xEF\x90\xB4" /*62516, f434*/ +#define LV_SYMBOL_EXT_BATH "\xEF\x8B\x8D" /*62157, f2cd*/ +#define LV_SYMBOL_EXT_BATTERY_EMPTY "\xEF\x89\x84" /*62020, f244*/ +#define LV_SYMBOL_EXT_BATTERY_FULL "\xEF\x89\x80" /*62016, f240*/ +#define LV_SYMBOL_EXT_BATTERY_HALF "\xEF\x89\x82" /*62018, f242*/ +#define LV_SYMBOL_EXT_BATTERY_QUARTER "\xEF\x89\x83" /*62019, f243*/ +#define LV_SYMBOL_EXT_BATTERY_THREE_QUARTERS "\xEF\x89\x81" /*62017, f241*/ +#define LV_SYMBOL_EXT_BATTLE_NET "\xEF\xA0\xB5" /*63541, f835*/ +#define LV_SYMBOL_EXT_BED "\xEF\x88\xB6" /*62006, f236*/ +#define LV_SYMBOL_EXT_BED_PULSE "\xEF\x92\x87" /*62599, f487*/ +#define LV_SYMBOL_EXT_BEER_MUG_EMPTY "\xEF\x83\xBC" /*61692, f0fc*/ +#define LV_SYMBOL_EXT_BEHANCE "\xEF\x86\xB4" /*61876, f1b4*/ +#define LV_SYMBOL_EXT_BELL "\xEF\x83\xB3" /*61683, f0f3*/ +#define LV_SYMBOL_EXT_BELL_CONCIERGE "\xEF\x95\xA2" /*62818, f562*/ +#define LV_SYMBOL_EXT_BELL_SLASH "\xEF\x87\xB6" /*61942, f1f6*/ +#define LV_SYMBOL_EXT_BEZIER_CURVE "\xEF\x95\x9B" /*62811, f55b*/ +#define LV_SYMBOL_EXT_BICYCLE "\xEF\x88\x86" /*61958, f206*/ +#define LV_SYMBOL_EXT_BILIBILI "\xEE\x8F\x99" /*58329, e3d9*/ +#define LV_SYMBOL_EXT_BIMOBJECT "\xEF\x8D\xB8" /*62328, f378*/ +#define LV_SYMBOL_EXT_BINOCULARS "\xEF\x87\xA5" /*61925, f1e5*/ +#define LV_SYMBOL_EXT_BIOHAZARD "\xEF\x9E\x80" /*63360, f780*/ +#define LV_SYMBOL_EXT_BITBUCKET "\xEF\x85\xB1" /*61809, f171*/ +#define LV_SYMBOL_EXT_BITCOIN "\xEF\x8D\xB9" /*62329, f379*/ +#define LV_SYMBOL_EXT_BITCOIN_SIGN "\xEE\x82\xB4" /*57524, e0b4*/ +#define LV_SYMBOL_EXT_BITY "\xEF\x8D\xBA" /*62330, f37a*/ +#define LV_SYMBOL_EXT_BLACK_TIE "\xEF\x89\xBE" /*62078, f27e*/ +#define LV_SYMBOL_EXT_BLACKBERRY "\xEF\x8D\xBB" /*62331, f37b*/ +#define LV_SYMBOL_EXT_BLENDER "\xEF\x94\x97" /*62743, f517*/ +#define LV_SYMBOL_EXT_BLENDER_PHONE "\xEF\x9A\xB6" /*63158, f6b6*/ +#define LV_SYMBOL_EXT_BLOG "\xEF\x9E\x81" /*63361, f781*/ +#define LV_SYMBOL_EXT_BLOGGER "\xEF\x8D\xBC" /*62332, f37c*/ +#define LV_SYMBOL_EXT_BLOGGER_B "\xEF\x8D\xBD" /*62333, f37d*/ +#define LV_SYMBOL_EXT_BLUETOOTH "\xEF\x8A\x93" /*62099, f293*/ +#define LV_SYMBOL_EXT_BLUETOOTH_B "\xEF\x8A\x94" /*62100, f294*/ +#define LV_SYMBOL_EXT_BOLD "\xEF\x80\xB2" /*61490, f032*/ +#define LV_SYMBOL_EXT_BOLT "\xEF\x83\xA7" /*61671, f0e7*/ +#define LV_SYMBOL_EXT_BOLT_LIGHTNING "\xEE\x82\xB7" /*57527, e0b7*/ +#define LV_SYMBOL_EXT_BOMB "\xEF\x87\xA2" /*61922, f1e2*/ +#define LV_SYMBOL_EXT_BONE "\xEF\x97\x97" /*62935, f5d7*/ +#define LV_SYMBOL_EXT_BONG "\xEF\x95\x9C" /*62812, f55c*/ +#define LV_SYMBOL_EXT_BOOK "\xEF\x80\xAD" /*61485, f02d*/ +#define LV_SYMBOL_EXT_BOOK_ATLAS "\xEF\x95\x98" /*62808, f558*/ +#define LV_SYMBOL_EXT_BOOK_BIBLE "\xEF\x99\x87" /*63047, f647*/ +#define LV_SYMBOL_EXT_BOOK_BOOKMARK "\xEE\x82\xBB" /*57531, e0bb*/ +#define LV_SYMBOL_EXT_BOOK_JOURNAL_WHILLS "\xEF\x99\xAA" /*63082, f66a*/ +#define LV_SYMBOL_EXT_BOOK_MEDICAL "\xEF\x9F\xA6" /*63462, f7e6*/ +#define LV_SYMBOL_EXT_BOOK_OPEN "\xEF\x94\x98" /*62744, f518*/ +#define LV_SYMBOL_EXT_BOOK_OPEN_READER "\xEF\x97\x9A" /*62938, f5da*/ +#define LV_SYMBOL_EXT_BOOK_QURAN "\xEF\x9A\x87" /*63111, f687*/ +#define LV_SYMBOL_EXT_BOOK_SKULL "\xEF\x9A\xB7" /*63159, f6b7*/ +#define LV_SYMBOL_EXT_BOOK_TANAKH "\xEF\xA0\xA7" /*63527, f827*/ +#define LV_SYMBOL_EXT_BOOKMARK "\xEF\x80\xAE" /*61486, f02e*/ +#define LV_SYMBOL_EXT_BOOTSTRAP "\xEF\xA0\xB6" /*63542, f836*/ +#define LV_SYMBOL_EXT_BORDER_ALL "\xEF\xA1\x8C" /*63564, f84c*/ +#define LV_SYMBOL_EXT_BORDER_NONE "\xEF\xA1\x90" /*63568, f850*/ +#define LV_SYMBOL_EXT_BORDER_TOP_LEFT "\xEF\xA1\x93" /*63571, f853*/ +#define LV_SYMBOL_EXT_BORE_HOLE "\xEE\x93\x83" /*58563, e4c3*/ +#define LV_SYMBOL_EXT_BOTS "\xEE\x8D\x80" /*58176, e340*/ +#define LV_SYMBOL_EXT_BOTTLE_DROPLET "\xEE\x93\x84" /*58564, e4c4*/ +#define LV_SYMBOL_EXT_BOTTLE_WATER "\xEE\x93\x85" /*58565, e4c5*/ +#define LV_SYMBOL_EXT_BOWL_FOOD "\xEE\x93\x86" /*58566, e4c6*/ +#define LV_SYMBOL_EXT_BOWL_RICE "\xEE\x8B\xAB" /*58091, e2eb*/ +#define LV_SYMBOL_EXT_BOWLING_BALL "\xEF\x90\xB6" /*62518, f436*/ +#define LV_SYMBOL_EXT_BOX "\xEF\x91\xA6" /*62566, f466*/ +#define LV_SYMBOL_EXT_BOX_ARCHIVE "\xEF\x86\x87" /*61831, f187*/ +#define LV_SYMBOL_EXT_BOX_OPEN "\xEF\x92\x9E" /*62622, f49e*/ +#define LV_SYMBOL_EXT_BOX_TISSUE "\xEE\x81\x9B" /*57435, e05b*/ +#define LV_SYMBOL_EXT_BOXES_PACKING "\xEE\x93\x87" /*58567, e4c7*/ +#define LV_SYMBOL_EXT_BOXES_STACKED "\xEF\x91\xA8" /*62568, f468*/ +#define LV_SYMBOL_EXT_BRAILLE "\xEF\x8A\xA1" /*62113, f2a1*/ +#define LV_SYMBOL_EXT_BRAIN "\xEF\x97\x9C" /*62940, f5dc*/ +#define LV_SYMBOL_EXT_BRAVE "\xEE\x98\xBC" /*58940, e63c*/ +#define LV_SYMBOL_EXT_BRAVE_REVERSE "\xEE\x98\xBD" /*58941, e63d*/ +#define LV_SYMBOL_EXT_BRAZILIAN_REAL_SIGN "\xEE\x91\xAC" /*58476, e46c*/ +#define LV_SYMBOL_EXT_BREAD_SLICE "\xEF\x9F\xAC" /*63468, f7ec*/ +#define LV_SYMBOL_EXT_BRIDGE "\xEE\x93\x88" /*58568, e4c8*/ +#define LV_SYMBOL_EXT_BRIDGE_CIRCLE_CHECK "\xEE\x93\x89" /*58569, e4c9*/ +#define LV_SYMBOL_EXT_BRIDGE_CIRCLE_EXCLAMATION "\xEE\x93\x8A" /*58570, e4ca*/ +#define LV_SYMBOL_EXT_BRIDGE_CIRCLE_XMARK "\xEE\x93\x8B" /*58571, e4cb*/ +#define LV_SYMBOL_EXT_BRIDGE_LOCK "\xEE\x93\x8C" /*58572, e4cc*/ +#define LV_SYMBOL_EXT_BRIDGE_WATER "\xEE\x93\x8E" /*58574, e4ce*/ +#define LV_SYMBOL_EXT_BRIEFCASE "\xEF\x82\xB1" /*61617, f0b1*/ +#define LV_SYMBOL_EXT_BRIEFCASE_MEDICAL "\xEF\x91\xA9" /*62569, f469*/ +#define LV_SYMBOL_EXT_BROOM "\xEF\x94\x9A" /*62746, f51a*/ +#define LV_SYMBOL_EXT_BROOM_BALL "\xEF\x91\x98" /*62552, f458*/ +#define LV_SYMBOL_EXT_BRUSH "\xEF\x95\x9D" /*62813, f55d*/ +#define LV_SYMBOL_EXT_BTC "\xEF\x85\x9A" /*61786, f15a*/ +#define LV_SYMBOL_EXT_BUCKET "\xEE\x93\x8F" /*58575, e4cf*/ +#define LV_SYMBOL_EXT_BUFFER "\xEF\xA0\xB7" /*63543, f837*/ +#define LV_SYMBOL_EXT_BUG "\xEF\x86\x88" /*61832, f188*/ +#define LV_SYMBOL_EXT_BUG_SLASH "\xEE\x92\x90" /*58512, e490*/ +#define LV_SYMBOL_EXT_BUGS "\xEE\x93\x90" /*58576, e4d0*/ +#define LV_SYMBOL_EXT_BUILDING "\xEF\x86\xAD" /*61869, f1ad*/ +#define LV_SYMBOL_EXT_BUILDING_CIRCLE_ARROW_RIGHT "\xEE\x93\x91" /*58577, e4d1*/ +#define LV_SYMBOL_EXT_BUILDING_CIRCLE_CHECK "\xEE\x93\x92" /*58578, e4d2*/ +#define LV_SYMBOL_EXT_BUILDING_CIRCLE_EXCLAMATION "\xEE\x93\x93" /*58579, e4d3*/ +#define LV_SYMBOL_EXT_BUILDING_CIRCLE_XMARK "\xEE\x93\x94" /*58580, e4d4*/ +#define LV_SYMBOL_EXT_BUILDING_COLUMNS "\xEF\x86\x9C" /*61852, f19c*/ +#define LV_SYMBOL_EXT_BUILDING_FLAG "\xEE\x93\x95" /*58581, e4d5*/ +#define LV_SYMBOL_EXT_BUILDING_LOCK "\xEE\x93\x96" /*58582, e4d6*/ +#define LV_SYMBOL_EXT_BUILDING_NGO "\xEE\x93\x97" /*58583, e4d7*/ +#define LV_SYMBOL_EXT_BUILDING_SHIELD "\xEE\x93\x98" /*58584, e4d8*/ +#define LV_SYMBOL_EXT_BUILDING_UN "\xEE\x93\x99" /*58585, e4d9*/ +#define LV_SYMBOL_EXT_BUILDING_USER "\xEE\x93\x9A" /*58586, e4da*/ +#define LV_SYMBOL_EXT_BUILDING_WHEAT "\xEE\x93\x9B" /*58587, e4db*/ +#define LV_SYMBOL_EXT_BULLHORN "\xEF\x82\xA1" /*61601, f0a1*/ +#define LV_SYMBOL_EXT_BULLSEYE "\xEF\x85\x80" /*61760, f140*/ +#define LV_SYMBOL_EXT_BURGER "\xEF\xA0\x85" /*63493, f805*/ +#define LV_SYMBOL_EXT_BUROMOBELEXPERTE "\xEF\x8D\xBF" /*62335, f37f*/ +#define LV_SYMBOL_EXT_BURST "\xEE\x93\x9C" /*58588, e4dc*/ +#define LV_SYMBOL_EXT_BUS "\xEF\x88\x87" /*61959, f207*/ +#define LV_SYMBOL_EXT_BUS_SIMPLE "\xEF\x95\x9E" /*62814, f55e*/ +#define LV_SYMBOL_EXT_BUSINESS_TIME "\xEF\x99\x8A" /*63050, f64a*/ +#define LV_SYMBOL_EXT_BUY_N_LARGE "\xEF\xA2\xA6" /*63654, f8a6*/ +#define LV_SYMBOL_EXT_BUYSELLADS "\xEF\x88\x8D" /*61965, f20d*/ +#define LV_SYMBOL_EXT_C "\x43" /*67, 43*/ +#define LV_SYMBOL_EXT_CABLE_CAR "\xEF\x9F\x9A" /*63450, f7da*/ +#define LV_SYMBOL_EXT_CAKE_CANDLES "\xEF\x87\xBD" /*61949, f1fd*/ +#define LV_SYMBOL_EXT_CALCULATOR "\xEF\x87\xAC" /*61932, f1ec*/ +#define LV_SYMBOL_EXT_CALENDAR "\xEF\x84\xB3" /*61747, f133*/ +#define LV_SYMBOL_EXT_CALENDAR_CHECK "\xEF\x89\xB4" /*62068, f274*/ +#define LV_SYMBOL_EXT_CALENDAR_DAY "\xEF\x9E\x83" /*63363, f783*/ +#define LV_SYMBOL_EXT_CALENDAR_DAYS "\xEF\x81\xB3" /*61555, f073*/ +#define LV_SYMBOL_EXT_CALENDAR_MINUS "\xEF\x89\xB2" /*62066, f272*/ +#define LV_SYMBOL_EXT_CALENDAR_PLUS "\xEF\x89\xB1" /*62065, f271*/ +#define LV_SYMBOL_EXT_CALENDAR_WEEK "\xEF\x9E\x84" /*63364, f784*/ +#define LV_SYMBOL_EXT_CALENDAR_XMARK "\xEF\x89\xB3" /*62067, f273*/ +#define LV_SYMBOL_EXT_CAMERA "\xEF\x80\xB0" /*61488, f030*/ +#define LV_SYMBOL_EXT_CAMERA_RETRO "\xEF\x82\x83" /*61571, f083*/ +#define LV_SYMBOL_EXT_CAMERA_ROTATE "\xEE\x83\x98" /*57560, e0d8*/ +#define LV_SYMBOL_EXT_CAMPGROUND "\xEF\x9A\xBB" /*63163, f6bb*/ +#define LV_SYMBOL_EXT_CANADIAN_MAPLE_LEAF "\xEF\x9E\x85" /*63365, f785*/ +#define LV_SYMBOL_EXT_CANDY_CANE "\xEF\x9E\x86" /*63366, f786*/ +#define LV_SYMBOL_EXT_CANNABIS "\xEF\x95\x9F" /*62815, f55f*/ +#define LV_SYMBOL_EXT_CAPSULES "\xEF\x91\xAB" /*62571, f46b*/ +#define LV_SYMBOL_EXT_CAR "\xEF\x86\xB9" /*61881, f1b9*/ +#define LV_SYMBOL_EXT_CAR_BATTERY "\xEF\x97\x9F" /*62943, f5df*/ +#define LV_SYMBOL_EXT_CAR_BURST "\xEF\x97\xA1" /*62945, f5e1*/ +#define LV_SYMBOL_EXT_CAR_ON "\xEE\x93\x9D" /*58589, e4dd*/ +#define LV_SYMBOL_EXT_CAR_REAR "\xEF\x97\x9E" /*62942, f5de*/ +#define LV_SYMBOL_EXT_CAR_SIDE "\xEF\x97\xA4" /*62948, f5e4*/ +#define LV_SYMBOL_EXT_CAR_TUNNEL "\xEE\x93\x9E" /*58590, e4de*/ +#define LV_SYMBOL_EXT_CARAVAN "\xEF\xA3\xBF" /*63743, f8ff*/ +#define LV_SYMBOL_EXT_CARET_DOWN "\xEF\x83\x97" /*61655, f0d7*/ +#define LV_SYMBOL_EXT_CARET_LEFT "\xEF\x83\x99" /*61657, f0d9*/ +#define LV_SYMBOL_EXT_CARET_RIGHT "\xEF\x83\x9A" /*61658, f0da*/ +#define LV_SYMBOL_EXT_CARET_UP "\xEF\x83\x98" /*61656, f0d8*/ +#define LV_SYMBOL_EXT_CARROT "\xEF\x9E\x87" /*63367, f787*/ +#define LV_SYMBOL_EXT_CART_ARROW_DOWN "\xEF\x88\x98" /*61976, f218*/ +#define LV_SYMBOL_EXT_CART_FLATBED "\xEF\x91\xB4" /*62580, f474*/ +#define LV_SYMBOL_EXT_CART_FLATBED_SUITCASE "\xEF\x96\x9D" /*62877, f59d*/ +#define LV_SYMBOL_EXT_CART_PLUS "\xEF\x88\x97" /*61975, f217*/ +#define LV_SYMBOL_EXT_CART_SHOPPING "\xEF\x81\xBA" /*61562, f07a*/ +#define LV_SYMBOL_EXT_CASH_REGISTER "\xEF\x9E\x88" /*63368, f788*/ +#define LV_SYMBOL_EXT_CAT "\xEF\x9A\xBE" /*63166, f6be*/ +#define LV_SYMBOL_EXT_CC_AMAZON_PAY "\xEF\x90\xAD" /*62509, f42d*/ +#define LV_SYMBOL_EXT_CC_AMEX "\xEF\x87\xB3" /*61939, f1f3*/ +#define LV_SYMBOL_EXT_CC_APPLE_PAY "\xEF\x90\x96" /*62486, f416*/ +#define LV_SYMBOL_EXT_CC_DINERS_CLUB "\xEF\x89\x8C" /*62028, f24c*/ +#define LV_SYMBOL_EXT_CC_DISCOVER "\xEF\x87\xB2" /*61938, f1f2*/ +#define LV_SYMBOL_EXT_CC_JCB "\xEF\x89\x8B" /*62027, f24b*/ +#define LV_SYMBOL_EXT_CC_MASTERCARD "\xEF\x87\xB1" /*61937, f1f1*/ +#define LV_SYMBOL_EXT_CC_PAYPAL "\xEF\x87\xB4" /*61940, f1f4*/ +#define LV_SYMBOL_EXT_CC_STRIPE "\xEF\x87\xB5" /*61941, f1f5*/ +#define LV_SYMBOL_EXT_CC_VISA "\xEF\x87\xB0" /*61936, f1f0*/ +#define LV_SYMBOL_EXT_CEDI_SIGN "\xEE\x83\x9F" /*57567, e0df*/ +#define LV_SYMBOL_EXT_CENT_SIGN "\xEE\x8F\xB5" /*58357, e3f5*/ +#define LV_SYMBOL_EXT_CENTERCODE "\xEF\x8E\x80" /*62336, f380*/ +#define LV_SYMBOL_EXT_CENTOS "\xEF\x9E\x89" /*63369, f789*/ +#define LV_SYMBOL_EXT_CERTIFICATE "\xEF\x82\xA3" /*61603, f0a3*/ +#define LV_SYMBOL_EXT_CHAIR "\xEF\x9B\x80" /*63168, f6c0*/ +#define LV_SYMBOL_EXT_CHALKBOARD "\xEF\x94\x9B" /*62747, f51b*/ +#define LV_SYMBOL_EXT_CHALKBOARD_USER "\xEF\x94\x9C" /*62748, f51c*/ +#define LV_SYMBOL_EXT_CHAMPAGNE_GLASSES "\xEF\x9E\x9F" /*63391, f79f*/ +#define LV_SYMBOL_EXT_CHARGING_STATION "\xEF\x97\xA7" /*62951, f5e7*/ +#define LV_SYMBOL_EXT_CHART_AREA "\xEF\x87\xBE" /*61950, f1fe*/ +#define LV_SYMBOL_EXT_CHART_BAR "\xEF\x82\x80" /*61568, f080*/ +#define LV_SYMBOL_EXT_CHART_COLUMN "\xEE\x83\xA3" /*57571, e0e3*/ +#define LV_SYMBOL_EXT_CHART_GANTT "\xEE\x83\xA4" /*57572, e0e4*/ +#define LV_SYMBOL_EXT_CHART_LINE "\xEF\x88\x81" /*61953, f201*/ +#define LV_SYMBOL_EXT_CHART_PIE "\xEF\x88\x80" /*61952, f200*/ +#define LV_SYMBOL_EXT_CHART_SIMPLE "\xEE\x91\xB3" /*58483, e473*/ +#define LV_SYMBOL_EXT_CHECK "\xEF\x80\x8C" /*61452, f00c*/ +#define LV_SYMBOL_EXT_CHECK_DOUBLE "\xEF\x95\xA0" /*62816, f560*/ +#define LV_SYMBOL_EXT_CHECK_TO_SLOT "\xEF\x9D\xB2" /*63346, f772*/ +#define LV_SYMBOL_EXT_CHEESE "\xEF\x9F\xAF" /*63471, f7ef*/ +#define LV_SYMBOL_EXT_CHESS "\xEF\x90\xB9" /*62521, f439*/ +#define LV_SYMBOL_EXT_CHESS_BISHOP "\xEF\x90\xBA" /*62522, f43a*/ +#define LV_SYMBOL_EXT_CHESS_BOARD "\xEF\x90\xBC" /*62524, f43c*/ +#define LV_SYMBOL_EXT_CHESS_KING "\xEF\x90\xBF" /*62527, f43f*/ +#define LV_SYMBOL_EXT_CHESS_KNIGHT "\xEF\x91\x81" /*62529, f441*/ +#define LV_SYMBOL_EXT_CHESS_PAWN "\xEF\x91\x83" /*62531, f443*/ +#define LV_SYMBOL_EXT_CHESS_QUEEN "\xEF\x91\x85" /*62533, f445*/ +#define LV_SYMBOL_EXT_CHESS_ROOK "\xEF\x91\x87" /*62535, f447*/ +#define LV_SYMBOL_EXT_CHEVRON_DOWN "\xEF\x81\xB8" /*61560, f078*/ +#define LV_SYMBOL_EXT_CHEVRON_LEFT "\xEF\x81\x93" /*61523, f053*/ +#define LV_SYMBOL_EXT_CHEVRON_RIGHT "\xEF\x81\x94" /*61524, f054*/ +#define LV_SYMBOL_EXT_CHEVRON_UP "\xEF\x81\xB7" /*61559, f077*/ +#define LV_SYMBOL_EXT_CHILD "\xEF\x86\xAE" /*61870, f1ae*/ +#define LV_SYMBOL_EXT_CHILD_COMBATANT "\xEE\x93\xA0" /*58592, e4e0*/ +#define LV_SYMBOL_EXT_CHILD_DRESS "\xEE\x96\x9C" /*58780, e59c*/ +#define LV_SYMBOL_EXT_CHILD_REACHING "\xEE\x96\x9D" /*58781, e59d*/ +#define LV_SYMBOL_EXT_CHILDREN "\xEE\x93\xA1" /*58593, e4e1*/ +#define LV_SYMBOL_EXT_CHROME "\xEF\x89\xA8" /*62056, f268*/ +#define LV_SYMBOL_EXT_CHROMECAST "\xEF\xA0\xB8" /*63544, f838*/ +#define LV_SYMBOL_EXT_CHURCH "\xEF\x94\x9D" /*62749, f51d*/ +#define LV_SYMBOL_EXT_CIRCLE "\xEF\x84\x91" /*61713, f111*/ +#define LV_SYMBOL_EXT_CIRCLE_ARROW_DOWN "\xEF\x82\xAB" /*61611, f0ab*/ +#define LV_SYMBOL_EXT_CIRCLE_ARROW_LEFT "\xEF\x82\xA8" /*61608, f0a8*/ +#define LV_SYMBOL_EXT_CIRCLE_ARROW_RIGHT "\xEF\x82\xA9" /*61609, f0a9*/ +#define LV_SYMBOL_EXT_CIRCLE_ARROW_UP "\xEF\x82\xAA" /*61610, f0aa*/ +#define LV_SYMBOL_EXT_CIRCLE_CHECK "\xEF\x81\x98" /*61528, f058*/ +#define LV_SYMBOL_EXT_CIRCLE_CHEVRON_DOWN "\xEF\x84\xBA" /*61754, f13a*/ +#define LV_SYMBOL_EXT_CIRCLE_CHEVRON_LEFT "\xEF\x84\xB7" /*61751, f137*/ +#define LV_SYMBOL_EXT_CIRCLE_CHEVRON_RIGHT "\xEF\x84\xB8" /*61752, f138*/ +#define LV_SYMBOL_EXT_CIRCLE_CHEVRON_UP "\xEF\x84\xB9" /*61753, f139*/ +#define LV_SYMBOL_EXT_CIRCLE_DOLLAR_TO_SLOT "\xEF\x92\xB9" /*62649, f4b9*/ +#define LV_SYMBOL_EXT_CIRCLE_DOT "\xEF\x86\x92" /*61842, f192*/ +#define LV_SYMBOL_EXT_CIRCLE_DOWN "\xEF\x8D\x98" /*62296, f358*/ +#define LV_SYMBOL_EXT_CIRCLE_EXCLAMATION "\xEF\x81\xAA" /*61546, f06a*/ +#define LV_SYMBOL_EXT_CIRCLE_H "\xEF\x91\xBE" /*62590, f47e*/ +#define LV_SYMBOL_EXT_CIRCLE_HALF_STROKE "\xEF\x81\x82" /*61506, f042*/ +#define LV_SYMBOL_EXT_CIRCLE_INFO "\xEF\x81\x9A" /*61530, f05a*/ +#define LV_SYMBOL_EXT_CIRCLE_LEFT "\xEF\x8D\x99" /*62297, f359*/ +#define LV_SYMBOL_EXT_CIRCLE_MINUS "\xEF\x81\x96" /*61526, f056*/ +#define LV_SYMBOL_EXT_CIRCLE_NODES "\xEE\x93\xA2" /*58594, e4e2*/ +#define LV_SYMBOL_EXT_CIRCLE_NOTCH "\xEF\x87\x8E" /*61902, f1ce*/ +#define LV_SYMBOL_EXT_CIRCLE_PAUSE "\xEF\x8A\x8B" /*62091, f28b*/ +#define LV_SYMBOL_EXT_CIRCLE_PLAY "\xEF\x85\x84" /*61764, f144*/ +#define LV_SYMBOL_EXT_CIRCLE_PLUS "\xEF\x81\x95" /*61525, f055*/ +#define LV_SYMBOL_EXT_CIRCLE_QUESTION "\xEF\x81\x99" /*61529, f059*/ +#define LV_SYMBOL_EXT_CIRCLE_RADIATION "\xEF\x9E\xBA" /*63418, f7ba*/ +#define LV_SYMBOL_EXT_CIRCLE_RIGHT "\xEF\x8D\x9A" /*62298, f35a*/ +#define LV_SYMBOL_EXT_CIRCLE_STOP "\xEF\x8A\x8D" /*62093, f28d*/ +#define LV_SYMBOL_EXT_CIRCLE_UP "\xEF\x8D\x9B" /*62299, f35b*/ +#define LV_SYMBOL_EXT_CIRCLE_USER "\xEF\x8A\xBD" /*62141, f2bd*/ +#define LV_SYMBOL_EXT_CIRCLE_XMARK "\xEF\x81\x97" /*61527, f057*/ +#define LV_SYMBOL_EXT_CITY "\xEF\x99\x8F" /*63055, f64f*/ +#define LV_SYMBOL_EXT_CLAPPERBOARD "\xEE\x84\xB1" /*57649, e131*/ +#define LV_SYMBOL_EXT_CLIPBOARD "\xEF\x8C\xA8" /*62248, f328*/ +#define LV_SYMBOL_EXT_CLIPBOARD_CHECK "\xEF\x91\xAC" /*62572, f46c*/ +#define LV_SYMBOL_EXT_CLIPBOARD_LIST "\xEF\x91\xAD" /*62573, f46d*/ +#define LV_SYMBOL_EXT_CLIPBOARD_QUESTION "\xEE\x93\xA3" /*58595, e4e3*/ +#define LV_SYMBOL_EXT_CLIPBOARD_USER "\xEF\x9F\xB3" /*63475, f7f3*/ +#define LV_SYMBOL_EXT_CLOCK "\xEF\x80\x97" /*61463, f017*/ +#define LV_SYMBOL_EXT_CLOCK_ROTATE_LEFT "\xEF\x87\x9A" /*61914, f1da*/ +#define LV_SYMBOL_EXT_CLONE "\xEF\x89\x8D" /*62029, f24d*/ +#define LV_SYMBOL_EXT_CLOSED_CAPTIONING "\xEF\x88\x8A" /*61962, f20a*/ +#define LV_SYMBOL_EXT_CLOUD "\xEF\x83\x82" /*61634, f0c2*/ +#define LV_SYMBOL_EXT_CLOUD_ARROW_DOWN "\xEF\x83\xAD" /*61677, f0ed*/ +#define LV_SYMBOL_EXT_CLOUD_ARROW_UP "\xEF\x83\xAE" /*61678, f0ee*/ +#define LV_SYMBOL_EXT_CLOUD_BOLT "\xEF\x9D\xAC" /*63340, f76c*/ +#define LV_SYMBOL_EXT_CLOUD_MEATBALL "\xEF\x9C\xBB" /*63291, f73b*/ +#define LV_SYMBOL_EXT_CLOUD_MOON "\xEF\x9B\x83" /*63171, f6c3*/ +#define LV_SYMBOL_EXT_CLOUD_MOON_RAIN "\xEF\x9C\xBC" /*63292, f73c*/ +#define LV_SYMBOL_EXT_CLOUD_RAIN "\xEF\x9C\xBD" /*63293, f73d*/ +#define LV_SYMBOL_EXT_CLOUD_SHOWERS_HEAVY "\xEF\x9D\x80" /*63296, f740*/ +#define LV_SYMBOL_EXT_CLOUD_SHOWERS_WATER "\xEE\x93\xA4" /*58596, e4e4*/ +#define LV_SYMBOL_EXT_CLOUD_SUN "\xEF\x9B\x84" /*63172, f6c4*/ +#define LV_SYMBOL_EXT_CLOUD_SUN_RAIN "\xEF\x9D\x83" /*63299, f743*/ +#define LV_SYMBOL_EXT_CLOUDFLARE "\xEE\x81\xBD" /*57469, e07d*/ +#define LV_SYMBOL_EXT_CLOUDSCALE "\xEF\x8E\x83" /*62339, f383*/ +#define LV_SYMBOL_EXT_CLOUDSMITH "\xEF\x8E\x84" /*62340, f384*/ +#define LV_SYMBOL_EXT_CLOUDVERSIFY "\xEF\x8E\x85" /*62341, f385*/ +#define LV_SYMBOL_EXT_CLOVER "\xEE\x84\xB9" /*57657, e139*/ +#define LV_SYMBOL_EXT_CMPLID "\xEE\x8D\xA0" /*58208, e360*/ +#define LV_SYMBOL_EXT_CODE "\xEF\x84\xA1" /*61729, f121*/ +#define LV_SYMBOL_EXT_CODE_BRANCH "\xEF\x84\xA6" /*61734, f126*/ +#define LV_SYMBOL_EXT_CODE_COMMIT "\xEF\x8E\x86" /*62342, f386*/ +#define LV_SYMBOL_EXT_CODE_COMPARE "\xEE\x84\xBA" /*57658, e13a*/ +#define LV_SYMBOL_EXT_CODE_FORK "\xEE\x84\xBB" /*57659, e13b*/ +#define LV_SYMBOL_EXT_CODE_MERGE "\xEF\x8E\x87" /*62343, f387*/ +#define LV_SYMBOL_EXT_CODE_PULL_REQUEST "\xEE\x84\xBC" /*57660, e13c*/ +#define LV_SYMBOL_EXT_CODEPEN "\xEF\x87\x8B" /*61899, f1cb*/ +#define LV_SYMBOL_EXT_CODIEPIE "\xEF\x8A\x84" /*62084, f284*/ +#define LV_SYMBOL_EXT_COINS "\xEF\x94\x9E" /*62750, f51e*/ +#define LV_SYMBOL_EXT_COLON_SIGN "\xEE\x85\x80" /*57664, e140*/ +#define LV_SYMBOL_EXT_COMMENT "\xEF\x81\xB5" /*61557, f075*/ +#define LV_SYMBOL_EXT_COMMENT_DOLLAR "\xEF\x99\x91" /*63057, f651*/ +#define LV_SYMBOL_EXT_COMMENT_DOTS "\xEF\x92\xAD" /*62637, f4ad*/ +#define LV_SYMBOL_EXT_COMMENT_MEDICAL "\xEF\x9F\xB5" /*63477, f7f5*/ +#define LV_SYMBOL_EXT_COMMENT_SLASH "\xEF\x92\xB3" /*62643, f4b3*/ +#define LV_SYMBOL_EXT_COMMENT_SMS "\xEF\x9F\x8D" /*63437, f7cd*/ +#define LV_SYMBOL_EXT_COMMENTS "\xEF\x82\x86" /*61574, f086*/ +#define LV_SYMBOL_EXT_COMMENTS_DOLLAR "\xEF\x99\x93" /*63059, f653*/ +#define LV_SYMBOL_EXT_COMPACT_DISC "\xEF\x94\x9F" /*62751, f51f*/ +#define LV_SYMBOL_EXT_COMPASS "\xEF\x85\x8E" /*61774, f14e*/ +#define LV_SYMBOL_EXT_COMPASS_DRAFTING "\xEF\x95\xA8" /*62824, f568*/ +#define LV_SYMBOL_EXT_COMPRESS "\xEF\x81\xA6" /*61542, f066*/ +#define LV_SYMBOL_EXT_COMPUTER "\xEE\x93\xA5" /*58597, e4e5*/ +#define LV_SYMBOL_EXT_COMPUTER_MOUSE "\xEF\xA3\x8C" /*63692, f8cc*/ +#define LV_SYMBOL_EXT_CONFLUENCE "\xEF\x9E\x8D" /*63373, f78d*/ +#define LV_SYMBOL_EXT_CONNECTDEVELOP "\xEF\x88\x8E" /*61966, f20e*/ +#define LV_SYMBOL_EXT_CONTAO "\xEF\x89\xAD" /*62061, f26d*/ +#define LV_SYMBOL_EXT_COOKIE "\xEF\x95\xA3" /*62819, f563*/ +#define LV_SYMBOL_EXT_COOKIE_BITE "\xEF\x95\xA4" /*62820, f564*/ +#define LV_SYMBOL_EXT_COPY "\xEF\x83\x85" /*61637, f0c5*/ +#define LV_SYMBOL_EXT_COPYRIGHT "\xEF\x87\xB9" /*61945, f1f9*/ +#define LV_SYMBOL_EXT_COTTON_BUREAU "\xEF\xA2\x9E" /*63646, f89e*/ +#define LV_SYMBOL_EXT_COUCH "\xEF\x92\xB8" /*62648, f4b8*/ +#define LV_SYMBOL_EXT_COW "\xEF\x9B\x88" /*63176, f6c8*/ +#define LV_SYMBOL_EXT_CPANEL "\xEF\x8E\x88" /*62344, f388*/ +#define LV_SYMBOL_EXT_CREATIVE_COMMONS "\xEF\x89\x9E" /*62046, f25e*/ +#define LV_SYMBOL_EXT_CREATIVE_COMMONS_BY "\xEF\x93\xA7" /*62695, f4e7*/ +#define LV_SYMBOL_EXT_CREATIVE_COMMONS_NC "\xEF\x93\xA8" /*62696, f4e8*/ +#define LV_SYMBOL_EXT_CREATIVE_COMMONS_NC_EU "\xEF\x93\xA9" /*62697, f4e9*/ +#define LV_SYMBOL_EXT_CREATIVE_COMMONS_NC_JP "\xEF\x93\xAA" /*62698, f4ea*/ +#define LV_SYMBOL_EXT_CREATIVE_COMMONS_ND "\xEF\x93\xAB" /*62699, f4eb*/ +#define LV_SYMBOL_EXT_CREATIVE_COMMONS_PD "\xEF\x93\xAC" /*62700, f4ec*/ +#define LV_SYMBOL_EXT_CREATIVE_COMMONS_PD_ALT "\xEF\x93\xAD" /*62701, f4ed*/ +#define LV_SYMBOL_EXT_CREATIVE_COMMONS_REMIX "\xEF\x93\xAE" /*62702, f4ee*/ +#define LV_SYMBOL_EXT_CREATIVE_COMMONS_SA "\xEF\x93\xAF" /*62703, f4ef*/ +#define LV_SYMBOL_EXT_CREATIVE_COMMONS_SAMPLING "\xEF\x93\xB0" /*62704, f4f0*/ +#define LV_SYMBOL_EXT_CREATIVE_COMMONS_SAMPLING_PLUS "\xEF\x93\xB1" /*62705, f4f1*/ +#define LV_SYMBOL_EXT_CREATIVE_COMMONS_SHARE "\xEF\x93\xB2" /*62706, f4f2*/ +#define LV_SYMBOL_EXT_CREATIVE_COMMONS_ZERO "\xEF\x93\xB3" /*62707, f4f3*/ +#define LV_SYMBOL_EXT_CREDIT_CARD "\xEF\x82\x9D" /*61597, f09d*/ +#define LV_SYMBOL_EXT_CRITICAL_ROLE "\xEF\x9B\x89" /*63177, f6c9*/ +#define LV_SYMBOL_EXT_CROP "\xEF\x84\xA5" /*61733, f125*/ +#define LV_SYMBOL_EXT_CROP_SIMPLE "\xEF\x95\xA5" /*62821, f565*/ +#define LV_SYMBOL_EXT_CROSS "\xEF\x99\x94" /*63060, f654*/ +#define LV_SYMBOL_EXT_CROSSHAIRS "\xEF\x81\x9B" /*61531, f05b*/ +#define LV_SYMBOL_EXT_CROW "\xEF\x94\xA0" /*62752, f520*/ +#define LV_SYMBOL_EXT_CROWN "\xEF\x94\xA1" /*62753, f521*/ +#define LV_SYMBOL_EXT_CRUTCH "\xEF\x9F\xB7" /*63479, f7f7*/ +#define LV_SYMBOL_EXT_CRUZEIRO_SIGN "\xEE\x85\x92" /*57682, e152*/ +#define LV_SYMBOL_EXT_CSS3 "\xEF\x84\xBC" /*61756, f13c*/ +#define LV_SYMBOL_EXT_CSS3_ALT "\xEF\x8E\x8B" /*62347, f38b*/ +#define LV_SYMBOL_EXT_CUBE "\xEF\x86\xB2" /*61874, f1b2*/ +#define LV_SYMBOL_EXT_CUBES "\xEF\x86\xB3" /*61875, f1b3*/ +#define LV_SYMBOL_EXT_CUBES_STACKED "\xEE\x93\xA6" /*58598, e4e6*/ +#define LV_SYMBOL_EXT_CUTTLEFISH "\xEF\x8E\x8C" /*62348, f38c*/ +#define LV_SYMBOL_EXT_D "\x44" /*68, 44*/ +#define LV_SYMBOL_EXT_D_AND_D "\xEF\x8E\x8D" /*62349, f38d*/ +#define LV_SYMBOL_EXT_D_AND_D_BEYOND "\xEF\x9B\x8A" /*63178, f6ca*/ +#define LV_SYMBOL_EXT_DAILYMOTION "\xEE\x81\x92" /*57426, e052*/ +#define LV_SYMBOL_EXT_DASHCUBE "\xEF\x88\x90" /*61968, f210*/ +#define LV_SYMBOL_EXT_DATABASE "\xEF\x87\x80" /*61888, f1c0*/ +#define LV_SYMBOL_EXT_DEBIAN "\xEE\x98\x8B" /*58891, e60b*/ +#define LV_SYMBOL_EXT_DEEZER "\xEE\x81\xB7" /*57463, e077*/ +#define LV_SYMBOL_EXT_DELETE_LEFT "\xEF\x95\x9A" /*62810, f55a*/ +#define LV_SYMBOL_EXT_DELICIOUS "\xEF\x86\xA5" /*61861, f1a5*/ +#define LV_SYMBOL_EXT_DEMOCRAT "\xEF\x9D\x87" /*63303, f747*/ +#define LV_SYMBOL_EXT_DEPLOYDOG "\xEF\x8E\x8E" /*62350, f38e*/ +#define LV_SYMBOL_EXT_DESKPRO "\xEF\x8E\x8F" /*62351, f38f*/ +#define LV_SYMBOL_EXT_DESKTOP "\xEF\x8E\x90" /*62352, f390*/ +#define LV_SYMBOL_EXT_DEV "\xEF\x9B\x8C" /*63180, f6cc*/ +#define LV_SYMBOL_EXT_DEVIANTART "\xEF\x86\xBD" /*61885, f1bd*/ +#define LV_SYMBOL_EXT_DHARMACHAKRA "\xEF\x99\x95" /*63061, f655*/ +#define LV_SYMBOL_EXT_DHL "\xEF\x9E\x90" /*63376, f790*/ +#define LV_SYMBOL_EXT_DIAGRAM_NEXT "\xEE\x91\xB6" /*58486, e476*/ +#define LV_SYMBOL_EXT_DIAGRAM_PREDECESSOR "\xEE\x91\xB7" /*58487, e477*/ +#define LV_SYMBOL_EXT_DIAGRAM_PROJECT "\xEF\x95\x82" /*62786, f542*/ +#define LV_SYMBOL_EXT_DIAGRAM_SUCCESSOR "\xEE\x91\xBA" /*58490, e47a*/ +#define LV_SYMBOL_EXT_DIAMOND "\xEF\x88\x99" /*61977, f219*/ +#define LV_SYMBOL_EXT_DIAMOND_TURN_RIGHT "\xEF\x97\xAB" /*62955, f5eb*/ +#define LV_SYMBOL_EXT_DIASPORA "\xEF\x9E\x91" /*63377, f791*/ +#define LV_SYMBOL_EXT_DICE "\xEF\x94\xA2" /*62754, f522*/ +#define LV_SYMBOL_EXT_DICE_D20 "\xEF\x9B\x8F" /*63183, f6cf*/ +#define LV_SYMBOL_EXT_DICE_D6 "\xEF\x9B\x91" /*63185, f6d1*/ +#define LV_SYMBOL_EXT_DICE_FIVE "\xEF\x94\xA3" /*62755, f523*/ +#define LV_SYMBOL_EXT_DICE_FOUR "\xEF\x94\xA4" /*62756, f524*/ +#define LV_SYMBOL_EXT_DICE_ONE "\xEF\x94\xA5" /*62757, f525*/ +#define LV_SYMBOL_EXT_DICE_SIX "\xEF\x94\xA6" /*62758, f526*/ +#define LV_SYMBOL_EXT_DICE_THREE "\xEF\x94\xA7" /*62759, f527*/ +#define LV_SYMBOL_EXT_DICE_TWO "\xEF\x94\xA8" /*62760, f528*/ +#define LV_SYMBOL_EXT_DIGG "\xEF\x86\xA6" /*61862, f1a6*/ +#define LV_SYMBOL_EXT_DIGITAL_OCEAN "\xEF\x8E\x91" /*62353, f391*/ +#define LV_SYMBOL_EXT_DISCORD "\xEF\x8E\x92" /*62354, f392*/ +#define LV_SYMBOL_EXT_DISCOURSE "\xEF\x8E\x93" /*62355, f393*/ +#define LV_SYMBOL_EXT_DISEASE "\xEF\x9F\xBA" /*63482, f7fa*/ +#define LV_SYMBOL_EXT_DISPLAY "\xEE\x85\xA3" /*57699, e163*/ +#define LV_SYMBOL_EXT_DIVIDE "\xEF\x94\xA9" /*62761, f529*/ +#define LV_SYMBOL_EXT_DNA "\xEF\x91\xB1" /*62577, f471*/ +#define LV_SYMBOL_EXT_DOCHUB "\xEF\x8E\x94" /*62356, f394*/ +#define LV_SYMBOL_EXT_DOCKER "\xEF\x8E\x95" /*62357, f395*/ +#define LV_SYMBOL_EXT_DOG "\xEF\x9B\x93" /*63187, f6d3*/ +#define LV_SYMBOL_EXT_DOLLAR_SIGN "\x24" /*36, 24*/ +#define LV_SYMBOL_EXT_DOLLY "\xEF\x91\xB2" /*62578, f472*/ +#define LV_SYMBOL_EXT_DONG_SIGN "\xEE\x85\xA9" /*57705, e169*/ +#define LV_SYMBOL_EXT_DOOR_CLOSED "\xEF\x94\xAA" /*62762, f52a*/ +#define LV_SYMBOL_EXT_DOOR_OPEN "\xEF\x94\xAB" /*62763, f52b*/ +#define LV_SYMBOL_EXT_DOVE "\xEF\x92\xBA" /*62650, f4ba*/ +#define LV_SYMBOL_EXT_DOWN_LEFT_AND_UP_RIGHT_TO_CENTER "\xEF\x90\xA2" /*62498, f422*/ +#define LV_SYMBOL_EXT_DOWN_LONG "\xEF\x8C\x89" /*62217, f309*/ +#define LV_SYMBOL_EXT_DOWNLOAD "\xEF\x80\x99" /*61465, f019*/ +#define LV_SYMBOL_EXT_DRAFT2DIGITAL "\xEF\x8E\x96" /*62358, f396*/ +#define LV_SYMBOL_EXT_DRAGON "\xEF\x9B\x95" /*63189, f6d5*/ +#define LV_SYMBOL_EXT_DRAW_POLYGON "\xEF\x97\xAE" /*62958, f5ee*/ +#define LV_SYMBOL_EXT_DRIBBBLE "\xEF\x85\xBD" /*61821, f17d*/ +#define LV_SYMBOL_EXT_DROPBOX "\xEF\x85\xAB" /*61803, f16b*/ +#define LV_SYMBOL_EXT_DROPLET "\xEF\x81\x83" /*61507, f043*/ +#define LV_SYMBOL_EXT_DROPLET_SLASH "\xEF\x97\x87" /*62919, f5c7*/ +#define LV_SYMBOL_EXT_DRUM "\xEF\x95\xA9" /*62825, f569*/ +#define LV_SYMBOL_EXT_DRUM_STEELPAN "\xEF\x95\xAA" /*62826, f56a*/ +#define LV_SYMBOL_EXT_DRUMSTICK_BITE "\xEF\x9B\x97" /*63191, f6d7*/ +#define LV_SYMBOL_EXT_DRUPAL "\xEF\x86\xA9" /*61865, f1a9*/ +#define LV_SYMBOL_EXT_DUMBBELL "\xEF\x91\x8B" /*62539, f44b*/ +#define LV_SYMBOL_EXT_DUMPSTER "\xEF\x9E\x93" /*63379, f793*/ +#define LV_SYMBOL_EXT_DUMPSTER_FIRE "\xEF\x9E\x94" /*63380, f794*/ +#define LV_SYMBOL_EXT_DUNGEON "\xEF\x9B\x99" /*63193, f6d9*/ +#define LV_SYMBOL_EXT_DYALOG "\xEF\x8E\x99" /*62361, f399*/ +#define LV_SYMBOL_EXT_E "\x45" /*69, 45*/ +#define LV_SYMBOL_EXT_EAR_DEAF "\xEF\x8A\xA4" /*62116, f2a4*/ +#define LV_SYMBOL_EXT_EAR_LISTEN "\xEF\x8A\xA2" /*62114, f2a2*/ +#define LV_SYMBOL_EXT_EARLYBIRDS "\xEF\x8E\x9A" /*62362, f39a*/ +#define LV_SYMBOL_EXT_EARTH_AFRICA "\xEF\x95\xBC" /*62844, f57c*/ +#define LV_SYMBOL_EXT_EARTH_AMERICAS "\xEF\x95\xBD" /*62845, f57d*/ +#define LV_SYMBOL_EXT_EARTH_ASIA "\xEF\x95\xBE" /*62846, f57e*/ +#define LV_SYMBOL_EXT_EARTH_EUROPE "\xEF\x9E\xA2" /*63394, f7a2*/ +#define LV_SYMBOL_EXT_EARTH_OCEANIA "\xEE\x91\xBB" /*58491, e47b*/ +#define LV_SYMBOL_EXT_EBAY "\xEF\x93\xB4" /*62708, f4f4*/ +#define LV_SYMBOL_EXT_EDGE "\xEF\x8A\x82" /*62082, f282*/ +#define LV_SYMBOL_EXT_EDGE_LEGACY "\xEE\x81\xB8" /*57464, e078*/ +#define LV_SYMBOL_EXT_EGG "\xEF\x9F\xBB" /*63483, f7fb*/ +#define LV_SYMBOL_EXT_EJECT "\xEF\x81\x92" /*61522, f052*/ +#define LV_SYMBOL_EXT_ELEMENTOR "\xEF\x90\xB0" /*62512, f430*/ +#define LV_SYMBOL_EXT_ELEVATOR "\xEE\x85\xAD" /*57709, e16d*/ +#define LV_SYMBOL_EXT_ELLIPSIS "\xEF\x85\x81" /*61761, f141*/ +#define LV_SYMBOL_EXT_ELLIPSIS_VERTICAL "\xEF\x85\x82" /*61762, f142*/ +#define LV_SYMBOL_EXT_ELLO "\xEF\x97\xB1" /*62961, f5f1*/ +#define LV_SYMBOL_EXT_EMBER "\xEF\x90\xA3" /*62499, f423*/ +#define LV_SYMBOL_EXT_EMPIRE "\xEF\x87\x91" /*61905, f1d1*/ +#define LV_SYMBOL_EXT_ENVELOPE "\xEF\x83\xA0" /*61664, f0e0*/ +#define LV_SYMBOL_EXT_ENVELOPE_CIRCLE_CHECK "\xEE\x93\xA8" /*58600, e4e8*/ +#define LV_SYMBOL_EXT_ENVELOPE_OPEN "\xEF\x8A\xB6" /*62134, f2b6*/ +#define LV_SYMBOL_EXT_ENVELOPE_OPEN_TEXT "\xEF\x99\x98" /*63064, f658*/ +#define LV_SYMBOL_EXT_ENVELOPES_BULK "\xEF\x99\xB4" /*63092, f674*/ +#define LV_SYMBOL_EXT_ENVIRA "\xEF\x8A\x99" /*62105, f299*/ +#define LV_SYMBOL_EXT_EQUALS "\x3D" /*61, 3d*/ +#define LV_SYMBOL_EXT_ERASER "\xEF\x84\xAD" /*61741, f12d*/ +#define LV_SYMBOL_EXT_ERLANG "\xEF\x8E\x9D" /*62365, f39d*/ +#define LV_SYMBOL_EXT_ETHEREUM "\xEF\x90\xAE" /*62510, f42e*/ +#define LV_SYMBOL_EXT_ETHERNET "\xEF\x9E\x96" /*63382, f796*/ +#define LV_SYMBOL_EXT_ETSY "\xEF\x8B\x97" /*62167, f2d7*/ +#define LV_SYMBOL_EXT_EURO_SIGN "\xEF\x85\x93" /*61779, f153*/ +#define LV_SYMBOL_EXT_EVERNOTE "\xEF\xA0\xB9" /*63545, f839*/ +#define LV_SYMBOL_EXT_EXCLAMATION "\x21" /*33, 21*/ +#define LV_SYMBOL_EXT_EXPAND "\xEF\x81\xA5" /*61541, f065*/ +#define LV_SYMBOL_EXT_EXPEDITEDSSL "\xEF\x88\xBE" /*62014, f23e*/ +#define LV_SYMBOL_EXT_EXPLOSION "\xEE\x93\xA9" /*58601, e4e9*/ +#define LV_SYMBOL_EXT_EYE "\xEF\x81\xAE" /*61550, f06e*/ +#define LV_SYMBOL_EXT_EYE_DROPPER "\xEF\x87\xBB" /*61947, f1fb*/ +#define LV_SYMBOL_EXT_EYE_LOW_VISION "\xEF\x8A\xA8" /*62120, f2a8*/ +#define LV_SYMBOL_EXT_EYE_SLASH "\xEF\x81\xB0" /*61552, f070*/ +#define LV_SYMBOL_EXT_F "\x46" /*70, 46*/ +#define LV_SYMBOL_EXT_FACE_ANGRY "\xEF\x95\x96" /*62806, f556*/ +#define LV_SYMBOL_EXT_FACE_DIZZY "\xEF\x95\xA7" /*62823, f567*/ +#define LV_SYMBOL_EXT_FACE_FLUSHED "\xEF\x95\xB9" /*62841, f579*/ +#define LV_SYMBOL_EXT_FACE_FROWN "\xEF\x84\x99" /*61721, f119*/ +#define LV_SYMBOL_EXT_FACE_FROWN_OPEN "\xEF\x95\xBA" /*62842, f57a*/ +#define LV_SYMBOL_EXT_FACE_GRIMACE "\xEF\x95\xBF" /*62847, f57f*/ +#define LV_SYMBOL_EXT_FACE_GRIN "\xEF\x96\x80" /*62848, f580*/ +#define LV_SYMBOL_EXT_FACE_GRIN_BEAM "\xEF\x96\x82" /*62850, f582*/ +#define LV_SYMBOL_EXT_FACE_GRIN_BEAM_SWEAT "\xEF\x96\x83" /*62851, f583*/ +#define LV_SYMBOL_EXT_FACE_GRIN_HEARTS "\xEF\x96\x84" /*62852, f584*/ +#define LV_SYMBOL_EXT_FACE_GRIN_SQUINT "\xEF\x96\x85" /*62853, f585*/ +#define LV_SYMBOL_EXT_FACE_GRIN_SQUINT_TEARS "\xEF\x96\x86" /*62854, f586*/ +#define LV_SYMBOL_EXT_FACE_GRIN_STARS "\xEF\x96\x87" /*62855, f587*/ +#define LV_SYMBOL_EXT_FACE_GRIN_TEARS "\xEF\x96\x88" /*62856, f588*/ +#define LV_SYMBOL_EXT_FACE_GRIN_TONGUE "\xEF\x96\x89" /*62857, f589*/ +#define LV_SYMBOL_EXT_FACE_GRIN_TONGUE_SQUINT "\xEF\x96\x8A" /*62858, f58a*/ +#define LV_SYMBOL_EXT_FACE_GRIN_TONGUE_WINK "\xEF\x96\x8B" /*62859, f58b*/ +#define LV_SYMBOL_EXT_FACE_GRIN_WIDE "\xEF\x96\x81" /*62849, f581*/ +#define LV_SYMBOL_EXT_FACE_GRIN_WINK "\xEF\x96\x8C" /*62860, f58c*/ +#define LV_SYMBOL_EXT_FACE_KISS "\xEF\x96\x96" /*62870, f596*/ +#define LV_SYMBOL_EXT_FACE_KISS_BEAM "\xEF\x96\x97" /*62871, f597*/ +#define LV_SYMBOL_EXT_FACE_KISS_WINK_HEART "\xEF\x96\x98" /*62872, f598*/ +#define LV_SYMBOL_EXT_FACE_LAUGH "\xEF\x96\x99" /*62873, f599*/ +#define LV_SYMBOL_EXT_FACE_LAUGH_BEAM "\xEF\x96\x9A" /*62874, f59a*/ +#define LV_SYMBOL_EXT_FACE_LAUGH_SQUINT "\xEF\x96\x9B" /*62875, f59b*/ +#define LV_SYMBOL_EXT_FACE_LAUGH_WINK "\xEF\x96\x9C" /*62876, f59c*/ +#define LV_SYMBOL_EXT_FACE_MEH "\xEF\x84\x9A" /*61722, f11a*/ +#define LV_SYMBOL_EXT_FACE_MEH_BLANK "\xEF\x96\xA4" /*62884, f5a4*/ +#define LV_SYMBOL_EXT_FACE_ROLLING_EYES "\xEF\x96\xA5" /*62885, f5a5*/ +#define LV_SYMBOL_EXT_FACE_SAD_CRY "\xEF\x96\xB3" /*62899, f5b3*/ +#define LV_SYMBOL_EXT_FACE_SAD_TEAR "\xEF\x96\xB4" /*62900, f5b4*/ +#define LV_SYMBOL_EXT_FACE_SMILE "\xEF\x84\x98" /*61720, f118*/ +#define LV_SYMBOL_EXT_FACE_SMILE_BEAM "\xEF\x96\xB8" /*62904, f5b8*/ +#define LV_SYMBOL_EXT_FACE_SMILE_WINK "\xEF\x93\x9A" /*62682, f4da*/ +#define LV_SYMBOL_EXT_FACE_SURPRISE "\xEF\x97\x82" /*62914, f5c2*/ +#define LV_SYMBOL_EXT_FACE_TIRED "\xEF\x97\x88" /*62920, f5c8*/ +#define LV_SYMBOL_EXT_FACEBOOK "\xEF\x82\x9A" /*61594, f09a*/ +#define LV_SYMBOL_EXT_FACEBOOK_F "\xEF\x8E\x9E" /*62366, f39e*/ +#define LV_SYMBOL_EXT_FACEBOOK_MESSENGER "\xEF\x8E\x9F" /*62367, f39f*/ +#define LV_SYMBOL_EXT_FAN "\xEF\xA1\xA3" /*63587, f863*/ +#define LV_SYMBOL_EXT_FANTASY_FLIGHT_GAMES "\xEF\x9B\x9C" /*63196, f6dc*/ +#define LV_SYMBOL_EXT_FAUCET "\xEE\x80\x85" /*57349, e005*/ +#define LV_SYMBOL_EXT_FAUCET_DRIP "\xEE\x80\x86" /*57350, e006*/ +#define LV_SYMBOL_EXT_FAX "\xEF\x86\xAC" /*61868, f1ac*/ +#define LV_SYMBOL_EXT_FEATHER "\xEF\x94\xAD" /*62765, f52d*/ +#define LV_SYMBOL_EXT_FEATHER_POINTED "\xEF\x95\xAB" /*62827, f56b*/ +#define LV_SYMBOL_EXT_FEDEX "\xEF\x9E\x97" /*63383, f797*/ +#define LV_SYMBOL_EXT_FEDORA "\xEF\x9E\x98" /*63384, f798*/ +#define LV_SYMBOL_EXT_FERRY "\xEE\x93\xAA" /*58602, e4ea*/ +#define LV_SYMBOL_EXT_FIGMA "\xEF\x9E\x99" /*63385, f799*/ +#define LV_SYMBOL_EXT_FILE "\xEF\x85\x9B" /*61787, f15b*/ +#define LV_SYMBOL_EXT_FILE_ARROW_DOWN "\xEF\x95\xAD" /*62829, f56d*/ +#define LV_SYMBOL_EXT_FILE_ARROW_UP "\xEF\x95\xB4" /*62836, f574*/ +#define LV_SYMBOL_EXT_FILE_AUDIO "\xEF\x87\x87" /*61895, f1c7*/ +#define LV_SYMBOL_EXT_FILE_CIRCLE_CHECK "\xEE\x96\xA0" /*58784, e5a0*/ +#define LV_SYMBOL_EXT_FILE_CIRCLE_EXCLAMATION "\xEE\x93\xAB" /*58603, e4eb*/ +#define LV_SYMBOL_EXT_FILE_CIRCLE_MINUS "\xEE\x93\xAD" /*58605, e4ed*/ +#define LV_SYMBOL_EXT_FILE_CIRCLE_PLUS "\xEE\x92\x94" /*58516, e494*/ +#define LV_SYMBOL_EXT_FILE_CIRCLE_QUESTION "\xEE\x93\xAF" /*58607, e4ef*/ +#define LV_SYMBOL_EXT_FILE_CIRCLE_XMARK "\xEE\x96\xA1" /*58785, e5a1*/ +#define LV_SYMBOL_EXT_FILE_CODE "\xEF\x87\x89" /*61897, f1c9*/ +#define LV_SYMBOL_EXT_FILE_CONTRACT "\xEF\x95\xAC" /*62828, f56c*/ +#define LV_SYMBOL_EXT_FILE_CSV "\xEF\x9B\x9D" /*63197, f6dd*/ +#define LV_SYMBOL_EXT_FILE_EXCEL "\xEF\x87\x83" /*61891, f1c3*/ +#define LV_SYMBOL_EXT_FILE_EXPORT "\xEF\x95\xAE" /*62830, f56e*/ +#define LV_SYMBOL_EXT_FILE_IMAGE "\xEF\x87\x85" /*61893, f1c5*/ +#define LV_SYMBOL_EXT_FILE_IMPORT "\xEF\x95\xAF" /*62831, f56f*/ +#define LV_SYMBOL_EXT_FILE_INVOICE "\xEF\x95\xB0" /*62832, f570*/ +#define LV_SYMBOL_EXT_FILE_INVOICE_DOLLAR "\xEF\x95\xB1" /*62833, f571*/ +#define LV_SYMBOL_EXT_FILE_LINES "\xEF\x85\x9C" /*61788, f15c*/ +#define LV_SYMBOL_EXT_FILE_MEDICAL "\xEF\x91\xB7" /*62583, f477*/ +#define LV_SYMBOL_EXT_FILE_PDF "\xEF\x87\x81" /*61889, f1c1*/ +#define LV_SYMBOL_EXT_FILE_PEN "\xEF\x8C\x9C" /*62236, f31c*/ +#define LV_SYMBOL_EXT_FILE_POWERPOINT "\xEF\x87\x84" /*61892, f1c4*/ +#define LV_SYMBOL_EXT_FILE_PRESCRIPTION "\xEF\x95\xB2" /*62834, f572*/ +#define LV_SYMBOL_EXT_FILE_SHIELD "\xEE\x93\xB0" /*58608, e4f0*/ +#define LV_SYMBOL_EXT_FILE_SIGNATURE "\xEF\x95\xB3" /*62835, f573*/ +#define LV_SYMBOL_EXT_FILE_VIDEO "\xEF\x87\x88" /*61896, f1c8*/ +#define LV_SYMBOL_EXT_FILE_WAVEFORM "\xEF\x91\xB8" /*62584, f478*/ +#define LV_SYMBOL_EXT_FILE_WORD "\xEF\x87\x82" /*61890, f1c2*/ +#define LV_SYMBOL_EXT_FILE_ZIPPER "\xEF\x87\x86" /*61894, f1c6*/ +#define LV_SYMBOL_EXT_FILL "\xEF\x95\xB5" /*62837, f575*/ +#define LV_SYMBOL_EXT_FILL_DRIP "\xEF\x95\xB6" /*62838, f576*/ +#define LV_SYMBOL_EXT_FILM "\xEF\x80\x88" /*61448, f008*/ +#define LV_SYMBOL_EXT_FILTER "\xEF\x82\xB0" /*61616, f0b0*/ +#define LV_SYMBOL_EXT_FILTER_CIRCLE_DOLLAR "\xEF\x99\xA2" /*63074, f662*/ +#define LV_SYMBOL_EXT_FILTER_CIRCLE_XMARK "\xEE\x85\xBB" /*57723, e17b*/ +#define LV_SYMBOL_EXT_FINGERPRINT "\xEF\x95\xB7" /*62839, f577*/ +#define LV_SYMBOL_EXT_FIRE "\xEF\x81\xAD" /*61549, f06d*/ +#define LV_SYMBOL_EXT_FIRE_BURNER "\xEE\x93\xB1" /*58609, e4f1*/ +#define LV_SYMBOL_EXT_FIRE_EXTINGUISHER "\xEF\x84\xB4" /*61748, f134*/ +#define LV_SYMBOL_EXT_FIRE_FLAME_CURVED "\xEF\x9F\xA4" /*63460, f7e4*/ +#define LV_SYMBOL_EXT_FIRE_FLAME_SIMPLE "\xEF\x91\xAA" /*62570, f46a*/ +#define LV_SYMBOL_EXT_FIREFOX "\xEF\x89\xA9" /*62057, f269*/ +#define LV_SYMBOL_EXT_FIREFOX_BROWSER "\xEE\x80\x87" /*57351, e007*/ +#define LV_SYMBOL_EXT_FIRST_ORDER "\xEF\x8A\xB0" /*62128, f2b0*/ +#define LV_SYMBOL_EXT_FIRST_ORDER_ALT "\xEF\x94\x8A" /*62730, f50a*/ +#define LV_SYMBOL_EXT_FIRSTDRAFT "\xEF\x8E\xA1" /*62369, f3a1*/ +#define LV_SYMBOL_EXT_FISH "\xEF\x95\xB8" /*62840, f578*/ +#define LV_SYMBOL_EXT_FISH_FINS "\xEE\x93\xB2" /*58610, e4f2*/ +#define LV_SYMBOL_EXT_FLAG "\xEF\x80\xA4" /*61476, f024*/ +#define LV_SYMBOL_EXT_FLAG_CHECKERED "\xEF\x84\x9E" /*61726, f11e*/ +#define LV_SYMBOL_EXT_FLAG_USA "\xEF\x9D\x8D" /*63309, f74d*/ +#define LV_SYMBOL_EXT_FLASK "\xEF\x83\x83" /*61635, f0c3*/ +#define LV_SYMBOL_EXT_FLASK_VIAL "\xEE\x93\xB3" /*58611, e4f3*/ +#define LV_SYMBOL_EXT_FLICKR "\xEF\x85\xAE" /*61806, f16e*/ +#define LV_SYMBOL_EXT_FLIPBOARD "\xEF\x91\x8D" /*62541, f44d*/ +#define LV_SYMBOL_EXT_FLOPPY_DISK "\xEF\x83\x87" /*61639, f0c7*/ +#define LV_SYMBOL_EXT_FLORIN_SIGN "\xEE\x86\x84" /*57732, e184*/ +#define LV_SYMBOL_EXT_FLY "\xEF\x90\x97" /*62487, f417*/ +#define LV_SYMBOL_EXT_FOLDER "\xEF\x81\xBB" /*61563, f07b*/ +#define LV_SYMBOL_EXT_FOLDER_CLOSED "\xEE\x86\x85" /*57733, e185*/ +#define LV_SYMBOL_EXT_FOLDER_MINUS "\xEF\x99\x9D" /*63069, f65d*/ +#define LV_SYMBOL_EXT_FOLDER_OPEN "\xEF\x81\xBC" /*61564, f07c*/ +#define LV_SYMBOL_EXT_FOLDER_PLUS "\xEF\x99\x9E" /*63070, f65e*/ +#define LV_SYMBOL_EXT_FOLDER_TREE "\xEF\xA0\x82" /*63490, f802*/ +#define LV_SYMBOL_EXT_FONT "\xEF\x80\xB1" /*61489, f031*/ +#define LV_SYMBOL_EXT_FONT_AWESOME "\xEF\x8A\xB4" /*62132, f2b4*/ +#define LV_SYMBOL_EXT_FONTICONS "\xEF\x8A\x80" /*62080, f280*/ +#define LV_SYMBOL_EXT_FONTICONS_FI "\xEF\x8E\xA2" /*62370, f3a2*/ +#define LV_SYMBOL_EXT_FOOTBALL "\xEF\x91\x8E" /*62542, f44e*/ +#define LV_SYMBOL_EXT_FORT_AWESOME "\xEF\x8A\x86" /*62086, f286*/ +#define LV_SYMBOL_EXT_FORT_AWESOME_ALT "\xEF\x8E\xA3" /*62371, f3a3*/ +#define LV_SYMBOL_EXT_FORUMBEE "\xEF\x88\x91" /*61969, f211*/ +#define LV_SYMBOL_EXT_FORWARD "\xEF\x81\x8E" /*61518, f04e*/ +#define LV_SYMBOL_EXT_FORWARD_FAST "\xEF\x81\x90" /*61520, f050*/ +#define LV_SYMBOL_EXT_FORWARD_STEP "\xEF\x81\x91" /*61521, f051*/ +#define LV_SYMBOL_EXT_FOURSQUARE "\xEF\x86\x80" /*61824, f180*/ +#define LV_SYMBOL_EXT_FRANC_SIGN "\xEE\x86\x8F" /*57743, e18f*/ +#define LV_SYMBOL_EXT_FREE_CODE_CAMP "\xEF\x8B\x85" /*62149, f2c5*/ +#define LV_SYMBOL_EXT_FREEBSD "\xEF\x8E\xA4" /*62372, f3a4*/ +#define LV_SYMBOL_EXT_FROG "\xEF\x94\xAE" /*62766, f52e*/ +#define LV_SYMBOL_EXT_FULCRUM "\xEF\x94\x8B" /*62731, f50b*/ +#define LV_SYMBOL_EXT_FUTBOL "\xEF\x87\xA3" /*61923, f1e3*/ +#define LV_SYMBOL_EXT_G "\x47" /*71, 47*/ +#define LV_SYMBOL_EXT_GALACTIC_REPUBLIC "\xEF\x94\x8C" /*62732, f50c*/ +#define LV_SYMBOL_EXT_GALACTIC_SENATE "\xEF\x94\x8D" /*62733, f50d*/ +#define LV_SYMBOL_EXT_GAMEPAD "\xEF\x84\x9B" /*61723, f11b*/ +#define LV_SYMBOL_EXT_GAS_PUMP "\xEF\x94\xAF" /*62767, f52f*/ +#define LV_SYMBOL_EXT_GAUGE "\xEF\x98\xA4" /*63012, f624*/ +#define LV_SYMBOL_EXT_GAUGE_HIGH "\xEF\x98\xA5" /*63013, f625*/ +#define LV_SYMBOL_EXT_GAUGE_SIMPLE "\xEF\x98\xA9" /*63017, f629*/ +#define LV_SYMBOL_EXT_GAUGE_SIMPLE_HIGH "\xEF\x98\xAA" /*63018, f62a*/ +#define LV_SYMBOL_EXT_GAVEL "\xEF\x83\xA3" /*61667, f0e3*/ +#define LV_SYMBOL_EXT_GEAR "\xEF\x80\x93" /*61459, f013*/ +#define LV_SYMBOL_EXT_GEARS "\xEF\x82\x85" /*61573, f085*/ +#define LV_SYMBOL_EXT_GEM "\xEF\x8E\xA5" /*62373, f3a5*/ +#define LV_SYMBOL_EXT_GENDERLESS "\xEF\x88\xAD" /*61997, f22d*/ +#define LV_SYMBOL_EXT_GET_POCKET "\xEF\x89\xA5" /*62053, f265*/ +#define LV_SYMBOL_EXT_GG "\xEF\x89\xA0" /*62048, f260*/ +#define LV_SYMBOL_EXT_GG_CIRCLE "\xEF\x89\xA1" /*62049, f261*/ +#define LV_SYMBOL_EXT_GHOST "\xEF\x9B\xA2" /*63202, f6e2*/ +#define LV_SYMBOL_EXT_GIFT "\xEF\x81\xAB" /*61547, f06b*/ +#define LV_SYMBOL_EXT_GIFTS "\xEF\x9E\x9C" /*63388, f79c*/ +#define LV_SYMBOL_EXT_GIT "\xEF\x87\x93" /*61907, f1d3*/ +#define LV_SYMBOL_EXT_GIT_ALT "\xEF\xA1\x81" /*63553, f841*/ +#define LV_SYMBOL_EXT_GITHUB "\xEF\x82\x9B" /*61595, f09b*/ +#define LV_SYMBOL_EXT_GITHUB_ALT "\xEF\x84\x93" /*61715, f113*/ +#define LV_SYMBOL_EXT_GITKRAKEN "\xEF\x8E\xA6" /*62374, f3a6*/ +#define LV_SYMBOL_EXT_GITLAB "\xEF\x8A\x96" /*62102, f296*/ +#define LV_SYMBOL_EXT_GITTER "\xEF\x90\xA6" /*62502, f426*/ +#define LV_SYMBOL_EXT_GLASS_WATER "\xEE\x93\xB4" /*58612, e4f4*/ +#define LV_SYMBOL_EXT_GLASS_WATER_DROPLET "\xEE\x93\xB5" /*58613, e4f5*/ +#define LV_SYMBOL_EXT_GLASSES "\xEF\x94\xB0" /*62768, f530*/ +#define LV_SYMBOL_EXT_GLIDE "\xEF\x8A\xA5" /*62117, f2a5*/ +#define LV_SYMBOL_EXT_GLIDE_G "\xEF\x8A\xA6" /*62118, f2a6*/ +#define LV_SYMBOL_EXT_GLOBE "\xEF\x82\xAC" /*61612, f0ac*/ +#define LV_SYMBOL_EXT_GOFORE "\xEF\x8E\xA7" /*62375, f3a7*/ +#define LV_SYMBOL_EXT_GOLANG "\xEE\x90\x8F" /*58383, e40f*/ +#define LV_SYMBOL_EXT_GOLF_BALL_TEE "\xEF\x91\x90" /*62544, f450*/ +#define LV_SYMBOL_EXT_GOODREADS "\xEF\x8E\xA8" /*62376, f3a8*/ +#define LV_SYMBOL_EXT_GOODREADS_G "\xEF\x8E\xA9" /*62377, f3a9*/ +#define LV_SYMBOL_EXT_GOOGLE "\xEF\x86\xA0" /*61856, f1a0*/ +#define LV_SYMBOL_EXT_GOOGLE_DRIVE "\xEF\x8E\xAA" /*62378, f3aa*/ +#define LV_SYMBOL_EXT_GOOGLE_PAY "\xEE\x81\xB9" /*57465, e079*/ +#define LV_SYMBOL_EXT_GOOGLE_PLAY "\xEF\x8E\xAB" /*62379, f3ab*/ +#define LV_SYMBOL_EXT_GOOGLE_PLUS "\xEF\x8A\xB3" /*62131, f2b3*/ +#define LV_SYMBOL_EXT_GOOGLE_PLUS_G "\xEF\x83\x95" /*61653, f0d5*/ +#define LV_SYMBOL_EXT_GOOGLE_SCHOLAR "\xEE\x98\xBB" /*58939, e63b*/ +#define LV_SYMBOL_EXT_GOOGLE_WALLET "\xEF\x87\xAE" /*61934, f1ee*/ +#define LV_SYMBOL_EXT_GOPURAM "\xEF\x99\xA4" /*63076, f664*/ +#define LV_SYMBOL_EXT_GRADUATION_CAP "\xEF\x86\x9D" /*61853, f19d*/ +#define LV_SYMBOL_EXT_GRATIPAY "\xEF\x86\x84" /*61828, f184*/ +#define LV_SYMBOL_EXT_GRAV "\xEF\x8B\x96" /*62166, f2d6*/ +#define LV_SYMBOL_EXT_GREATER_THAN "\x3E" /*62, 3e*/ +#define LV_SYMBOL_EXT_GREATER_THAN_EQUAL "\xEF\x94\xB2" /*62770, f532*/ +#define LV_SYMBOL_EXT_GRIP "\xEF\x96\x8D" /*62861, f58d*/ +#define LV_SYMBOL_EXT_GRIP_LINES "\xEF\x9E\xA4" /*63396, f7a4*/ +#define LV_SYMBOL_EXT_GRIP_LINES_VERTICAL "\xEF\x9E\xA5" /*63397, f7a5*/ +#define LV_SYMBOL_EXT_GRIP_VERTICAL "\xEF\x96\x8E" /*62862, f58e*/ +#define LV_SYMBOL_EXT_GRIPFIRE "\xEF\x8E\xAC" /*62380, f3ac*/ +#define LV_SYMBOL_EXT_GROUP_ARROWS_ROTATE "\xEE\x93\xB6" /*58614, e4f6*/ +#define LV_SYMBOL_EXT_GRUNT "\xEF\x8E\xAD" /*62381, f3ad*/ +#define LV_SYMBOL_EXT_GUARANI_SIGN "\xEE\x86\x9A" /*57754, e19a*/ +#define LV_SYMBOL_EXT_GUILDED "\xEE\x81\xBE" /*57470, e07e*/ +#define LV_SYMBOL_EXT_GUITAR "\xEF\x9E\xA6" /*63398, f7a6*/ +#define LV_SYMBOL_EXT_GULP "\xEF\x8E\xAE" /*62382, f3ae*/ +#define LV_SYMBOL_EXT_GUN "\xEE\x86\x9B" /*57755, e19b*/ +#define LV_SYMBOL_EXT_H "\x48" /*72, 48*/ +#define LV_SYMBOL_EXT_HACKER_NEWS "\xEF\x87\x94" /*61908, f1d4*/ +#define LV_SYMBOL_EXT_HACKERRANK "\xEF\x97\xB7" /*62967, f5f7*/ +#define LV_SYMBOL_EXT_HAMMER "\xEF\x9B\xA3" /*63203, f6e3*/ +#define LV_SYMBOL_EXT_HAMSA "\xEF\x99\xA5" /*63077, f665*/ +#define LV_SYMBOL_EXT_HAND "\xEF\x89\x96" /*62038, f256*/ +#define LV_SYMBOL_EXT_HAND_BACK_FIST "\xEF\x89\x95" /*62037, f255*/ +#define LV_SYMBOL_EXT_HAND_DOTS "\xEF\x91\xA1" /*62561, f461*/ +#define LV_SYMBOL_EXT_HAND_FIST "\xEF\x9B\x9E" /*63198, f6de*/ +#define LV_SYMBOL_EXT_HAND_HOLDING "\xEF\x92\xBD" /*62653, f4bd*/ +#define LV_SYMBOL_EXT_HAND_HOLDING_DOLLAR "\xEF\x93\x80" /*62656, f4c0*/ +#define LV_SYMBOL_EXT_HAND_HOLDING_DROPLET "\xEF\x93\x81" /*62657, f4c1*/ +#define LV_SYMBOL_EXT_HAND_HOLDING_HAND "\xEE\x93\xB7" /*58615, e4f7*/ +#define LV_SYMBOL_EXT_HAND_HOLDING_HEART "\xEF\x92\xBE" /*62654, f4be*/ +#define LV_SYMBOL_EXT_HAND_HOLDING_MEDICAL "\xEE\x81\x9C" /*57436, e05c*/ +#define LV_SYMBOL_EXT_HAND_LIZARD "\xEF\x89\x98" /*62040, f258*/ +#define LV_SYMBOL_EXT_HAND_MIDDLE_FINGER "\xEF\xA0\x86" /*63494, f806*/ +#define LV_SYMBOL_EXT_HAND_PEACE "\xEF\x89\x9B" /*62043, f25b*/ +#define LV_SYMBOL_EXT_HAND_POINT_DOWN "\xEF\x82\xA7" /*61607, f0a7*/ +#define LV_SYMBOL_EXT_HAND_POINT_LEFT "\xEF\x82\xA5" /*61605, f0a5*/ +#define LV_SYMBOL_EXT_HAND_POINT_RIGHT "\xEF\x82\xA4" /*61604, f0a4*/ +#define LV_SYMBOL_EXT_HAND_POINT_UP "\xEF\x82\xA6" /*61606, f0a6*/ +#define LV_SYMBOL_EXT_HAND_POINTER "\xEF\x89\x9A" /*62042, f25a*/ +#define LV_SYMBOL_EXT_HAND_SCISSORS "\xEF\x89\x97" /*62039, f257*/ +#define LV_SYMBOL_EXT_HAND_SPARKLES "\xEE\x81\x9D" /*57437, e05d*/ +#define LV_SYMBOL_EXT_HAND_SPOCK "\xEF\x89\x99" /*62041, f259*/ +#define LV_SYMBOL_EXT_HANDCUFFS "\xEE\x93\xB8" /*58616, e4f8*/ +#define LV_SYMBOL_EXT_HANDS "\xEF\x8A\xA7" /*62119, f2a7*/ +#define LV_SYMBOL_EXT_HANDS_ASL_INTERPRETING "\xEF\x8A\xA3" /*62115, f2a3*/ +#define LV_SYMBOL_EXT_HANDS_BOUND "\xEE\x93\xB9" /*58617, e4f9*/ +#define LV_SYMBOL_EXT_HANDS_BUBBLES "\xEE\x81\x9E" /*57438, e05e*/ +#define LV_SYMBOL_EXT_HANDS_CLAPPING "\xEE\x86\xA8" /*57768, e1a8*/ +#define LV_SYMBOL_EXT_HANDS_HOLDING "\xEF\x93\x82" /*62658, f4c2*/ +#define LV_SYMBOL_EXT_HANDS_HOLDING_CHILD "\xEE\x93\xBA" /*58618, e4fa*/ +#define LV_SYMBOL_EXT_HANDS_HOLDING_CIRCLE "\xEE\x93\xBB" /*58619, e4fb*/ +#define LV_SYMBOL_EXT_HANDS_PRAYING "\xEF\x9A\x84" /*63108, f684*/ +#define LV_SYMBOL_EXT_HANDSHAKE "\xEF\x8A\xB5" /*62133, f2b5*/ +#define LV_SYMBOL_EXT_HANDSHAKE_ANGLE "\xEF\x93\x84" /*62660, f4c4*/ +#define LV_SYMBOL_EXT_HANDSHAKE_SIMPLE "\xEF\x93\x86" /*62662, f4c6*/ +#define LV_SYMBOL_EXT_HANDSHAKE_SIMPLE_SLASH "\xEE\x81\x9F" /*57439, e05f*/ +#define LV_SYMBOL_EXT_HANDSHAKE_SLASH "\xEE\x81\xA0" /*57440, e060*/ +#define LV_SYMBOL_EXT_HANUKIAH "\xEF\x9B\xA6" /*63206, f6e6*/ +#define LV_SYMBOL_EXT_HARD_DRIVE "\xEF\x82\xA0" /*61600, f0a0*/ +#define LV_SYMBOL_EXT_HASHNODE "\xEE\x92\x99" /*58521, e499*/ +#define LV_SYMBOL_EXT_HASHTAG "\x23" /*35, 23*/ +#define LV_SYMBOL_EXT_HAT_COWBOY "\xEF\xA3\x80" /*63680, f8c0*/ +#define LV_SYMBOL_EXT_HAT_COWBOY_SIDE "\xEF\xA3\x81" /*63681, f8c1*/ +#define LV_SYMBOL_EXT_HAT_WIZARD "\xEF\x9B\xA8" /*63208, f6e8*/ +#define LV_SYMBOL_EXT_HEAD_SIDE_COUGH "\xEE\x81\xA1" /*57441, e061*/ +#define LV_SYMBOL_EXT_HEAD_SIDE_COUGH_SLASH "\xEE\x81\xA2" /*57442, e062*/ +#define LV_SYMBOL_EXT_HEAD_SIDE_MASK "\xEE\x81\xA3" /*57443, e063*/ +#define LV_SYMBOL_EXT_HEAD_SIDE_VIRUS "\xEE\x81\xA4" /*57444, e064*/ +#define LV_SYMBOL_EXT_HEADING "\xEF\x87\x9C" /*61916, f1dc*/ +#define LV_SYMBOL_EXT_HEADPHONES "\xEF\x80\xA5" /*61477, f025*/ +#define LV_SYMBOL_EXT_HEADPHONES_SIMPLE "\xEF\x96\x8F" /*62863, f58f*/ +#define LV_SYMBOL_EXT_HEADSET "\xEF\x96\x90" /*62864, f590*/ +#define LV_SYMBOL_EXT_HEART "\xEF\x80\x84" /*61444, f004*/ +#define LV_SYMBOL_EXT_HEART_CIRCLE_BOLT "\xEE\x93\xBC" /*58620, e4fc*/ +#define LV_SYMBOL_EXT_HEART_CIRCLE_CHECK "\xEE\x93\xBD" /*58621, e4fd*/ +#define LV_SYMBOL_EXT_HEART_CIRCLE_EXCLAMATION "\xEE\x93\xBE" /*58622, e4fe*/ +#define LV_SYMBOL_EXT_HEART_CIRCLE_MINUS "\xEE\x93\xBF" /*58623, e4ff*/ +#define LV_SYMBOL_EXT_HEART_CIRCLE_PLUS "\xEE\x94\x80" /*58624, e500*/ +#define LV_SYMBOL_EXT_HEART_CIRCLE_XMARK "\xEE\x94\x81" /*58625, e501*/ +#define LV_SYMBOL_EXT_HEART_CRACK "\xEF\x9E\xA9" /*63401, f7a9*/ +#define LV_SYMBOL_EXT_HEART_PULSE "\xEF\x88\x9E" /*61982, f21e*/ +#define LV_SYMBOL_EXT_HELICOPTER "\xEF\x94\xB3" /*62771, f533*/ +#define LV_SYMBOL_EXT_HELICOPTER_SYMBOL "\xEE\x94\x82" /*58626, e502*/ +#define LV_SYMBOL_EXT_HELMET_SAFETY "\xEF\xA0\x87" /*63495, f807*/ +#define LV_SYMBOL_EXT_HELMET_UN "\xEE\x94\x83" /*58627, e503*/ +#define LV_SYMBOL_EXT_HIGHLIGHTER "\xEF\x96\x91" /*62865, f591*/ +#define LV_SYMBOL_EXT_HILL_AVALANCHE "\xEE\x94\x87" /*58631, e507*/ +#define LV_SYMBOL_EXT_HILL_ROCKSLIDE "\xEE\x94\x88" /*58632, e508*/ +#define LV_SYMBOL_EXT_HIPPO "\xEF\x9B\xAD" /*63213, f6ed*/ +#define LV_SYMBOL_EXT_HIPS "\xEF\x91\x92" /*62546, f452*/ +#define LV_SYMBOL_EXT_HIRE_A_HELPER "\xEF\x8E\xB0" /*62384, f3b0*/ +#define LV_SYMBOL_EXT_HIVE "\xEE\x81\xBF" /*57471, e07f*/ +#define LV_SYMBOL_EXT_HOCKEY_PUCK "\xEF\x91\x93" /*62547, f453*/ +#define LV_SYMBOL_EXT_HOLLY_BERRY "\xEF\x9E\xAA" /*63402, f7aa*/ +#define LV_SYMBOL_EXT_HOOLI "\xEF\x90\xA7" /*62503, f427*/ +#define LV_SYMBOL_EXT_HORNBILL "\xEF\x96\x92" /*62866, f592*/ +#define LV_SYMBOL_EXT_HORSE "\xEF\x9B\xB0" /*63216, f6f0*/ +#define LV_SYMBOL_EXT_HORSE_HEAD "\xEF\x9E\xAB" /*63403, f7ab*/ +#define LV_SYMBOL_EXT_HOSPITAL "\xEF\x83\xB8" /*61688, f0f8*/ +#define LV_SYMBOL_EXT_HOSPITAL_USER "\xEF\xA0\x8D" /*63501, f80d*/ +#define LV_SYMBOL_EXT_HOT_TUB_PERSON "\xEF\x96\x93" /*62867, f593*/ +#define LV_SYMBOL_EXT_HOTDOG "\xEF\xA0\x8F" /*63503, f80f*/ +#define LV_SYMBOL_EXT_HOTEL "\xEF\x96\x94" /*62868, f594*/ +#define LV_SYMBOL_EXT_HOTJAR "\xEF\x8E\xB1" /*62385, f3b1*/ +#define LV_SYMBOL_EXT_HOURGLASS "\xEF\x89\x94" /*62036, f254*/ +#define LV_SYMBOL_EXT_HOURGLASS_END "\xEF\x89\x93" /*62035, f253*/ +#define LV_SYMBOL_EXT_HOURGLASS_HALF "\xEF\x89\x92" /*62034, f252*/ +#define LV_SYMBOL_EXT_HOURGLASS_START "\xEF\x89\x91" /*62033, f251*/ +#define LV_SYMBOL_EXT_HOUSE "\xEF\x80\x95" /*61461, f015*/ +#define LV_SYMBOL_EXT_HOUSE_CHIMNEY "\xEE\x8E\xAF" /*58287, e3af*/ +#define LV_SYMBOL_EXT_HOUSE_CHIMNEY_CRACK "\xEF\x9B\xB1" /*63217, f6f1*/ +#define LV_SYMBOL_EXT_HOUSE_CHIMNEY_MEDICAL "\xEF\x9F\xB2" /*63474, f7f2*/ +#define LV_SYMBOL_EXT_HOUSE_CHIMNEY_USER "\xEE\x81\xA5" /*57445, e065*/ +#define LV_SYMBOL_EXT_HOUSE_CHIMNEY_WINDOW "\xEE\x80\x8D" /*57357, e00d*/ +#define LV_SYMBOL_EXT_HOUSE_CIRCLE_CHECK "\xEE\x94\x89" /*58633, e509*/ +#define LV_SYMBOL_EXT_HOUSE_CIRCLE_EXCLAMATION "\xEE\x94\x8A" /*58634, e50a*/ +#define LV_SYMBOL_EXT_HOUSE_CIRCLE_XMARK "\xEE\x94\x8B" /*58635, e50b*/ +#define LV_SYMBOL_EXT_HOUSE_CRACK "\xEE\x8E\xB1" /*58289, e3b1*/ +#define LV_SYMBOL_EXT_HOUSE_FIRE "\xEE\x94\x8C" /*58636, e50c*/ +#define LV_SYMBOL_EXT_HOUSE_FLAG "\xEE\x94\x8D" /*58637, e50d*/ +#define LV_SYMBOL_EXT_HOUSE_FLOOD_WATER "\xEE\x94\x8E" /*58638, e50e*/ +#define LV_SYMBOL_EXT_HOUSE_FLOOD_WATER_CIRCLE_ARROW_RIGHT "\xEE\x94\x8F" /*58639, e50f*/ +#define LV_SYMBOL_EXT_HOUSE_LAPTOP "\xEE\x81\xA6" /*57446, e066*/ +#define LV_SYMBOL_EXT_HOUSE_LOCK "\xEE\x94\x90" /*58640, e510*/ +#define LV_SYMBOL_EXT_HOUSE_MEDICAL "\xEE\x8E\xB2" /*58290, e3b2*/ +#define LV_SYMBOL_EXT_HOUSE_MEDICAL_CIRCLE_CHECK "\xEE\x94\x91" /*58641, e511*/ +#define LV_SYMBOL_EXT_HOUSE_MEDICAL_CIRCLE_EXCLAMATION "\xEE\x94\x92" /*58642, e512*/ +#define LV_SYMBOL_EXT_HOUSE_MEDICAL_CIRCLE_XMARK "\xEE\x94\x93" /*58643, e513*/ +#define LV_SYMBOL_EXT_HOUSE_MEDICAL_FLAG "\xEE\x94\x94" /*58644, e514*/ +#define LV_SYMBOL_EXT_HOUSE_SIGNAL "\xEE\x80\x92" /*57362, e012*/ +#define LV_SYMBOL_EXT_HOUSE_TSUNAMI "\xEE\x94\x95" /*58645, e515*/ +#define LV_SYMBOL_EXT_HOUSE_USER "\xEE\x86\xB0" /*57776, e1b0*/ +#define LV_SYMBOL_EXT_HOUZZ "\xEF\x89\xBC" /*62076, f27c*/ +#define LV_SYMBOL_EXT_HRYVNIA_SIGN "\xEF\x9B\xB2" /*63218, f6f2*/ +#define LV_SYMBOL_EXT_HTML5 "\xEF\x84\xBB" /*61755, f13b*/ +#define LV_SYMBOL_EXT_HUBSPOT "\xEF\x8E\xB2" /*62386, f3b2*/ +#define LV_SYMBOL_EXT_HURRICANE "\xEF\x9D\x91" /*63313, f751*/ +#define LV_SYMBOL_EXT_I "\x49" /*73, 49*/ +#define LV_SYMBOL_EXT_I_CURSOR "\xEF\x89\x86" /*62022, f246*/ +#define LV_SYMBOL_EXT_ICE_CREAM "\xEF\xA0\x90" /*63504, f810*/ +#define LV_SYMBOL_EXT_ICICLES "\xEF\x9E\xAD" /*63405, f7ad*/ +#define LV_SYMBOL_EXT_ICONS "\xEF\xA1\xAD" /*63597, f86d*/ +#define LV_SYMBOL_EXT_ID_BADGE "\xEF\x8B\x81" /*62145, f2c1*/ +#define LV_SYMBOL_EXT_ID_CARD "\xEF\x8B\x82" /*62146, f2c2*/ +#define LV_SYMBOL_EXT_ID_CARD_CLIP "\xEF\x91\xBF" /*62591, f47f*/ +#define LV_SYMBOL_EXT_IDEAL "\xEE\x80\x93" /*57363, e013*/ +#define LV_SYMBOL_EXT_IGLOO "\xEF\x9E\xAE" /*63406, f7ae*/ +#define LV_SYMBOL_EXT_IMAGE "\xEF\x80\xBE" /*61502, f03e*/ +#define LV_SYMBOL_EXT_IMAGE_PORTRAIT "\xEF\x8F\xA0" /*62432, f3e0*/ +#define LV_SYMBOL_EXT_IMAGES "\xEF\x8C\x82" /*62210, f302*/ +#define LV_SYMBOL_EXT_IMDB "\xEF\x8B\x98" /*62168, f2d8*/ +#define LV_SYMBOL_EXT_INBOX "\xEF\x80\x9C" /*61468, f01c*/ +#define LV_SYMBOL_EXT_INDENT "\xEF\x80\xBC" /*61500, f03c*/ +#define LV_SYMBOL_EXT_INDIAN_RUPEE_SIGN "\xEE\x86\xBC" /*57788, e1bc*/ +#define LV_SYMBOL_EXT_INDUSTRY "\xEF\x89\xB5" /*62069, f275*/ +#define LV_SYMBOL_EXT_INFINITY "\xEF\x94\xB4" /*62772, f534*/ +#define LV_SYMBOL_EXT_INFO "\xEF\x84\xA9" /*61737, f129*/ +#define LV_SYMBOL_EXT_INSTAGRAM "\xEF\x85\xAD" /*61805, f16d*/ +#define LV_SYMBOL_EXT_INSTALOD "\xEE\x82\x81" /*57473, e081*/ +#define LV_SYMBOL_EXT_INTERCOM "\xEF\x9E\xAF" /*63407, f7af*/ +#define LV_SYMBOL_EXT_INTERNET_EXPLORER "\xEF\x89\xAB" /*62059, f26b*/ +#define LV_SYMBOL_EXT_INVISION "\xEF\x9E\xB0" /*63408, f7b0*/ +#define LV_SYMBOL_EXT_IOXHOST "\xEF\x88\x88" /*61960, f208*/ +#define LV_SYMBOL_EXT_ITALIC "\xEF\x80\xB3" /*61491, f033*/ +#define LV_SYMBOL_EXT_ITCH_IO "\xEF\xA0\xBA" /*63546, f83a*/ +#define LV_SYMBOL_EXT_ITUNES "\xEF\x8E\xB4" /*62388, f3b4*/ +#define LV_SYMBOL_EXT_ITUNES_NOTE "\xEF\x8E\xB5" /*62389, f3b5*/ +#define LV_SYMBOL_EXT_J "\x4A" /*74, 4a*/ +#define LV_SYMBOL_EXT_JAR "\xEE\x94\x96" /*58646, e516*/ +#define LV_SYMBOL_EXT_JAR_WHEAT "\xEE\x94\x97" /*58647, e517*/ +#define LV_SYMBOL_EXT_JAVA "\xEF\x93\xA4" /*62692, f4e4*/ +#define LV_SYMBOL_EXT_JEDI "\xEF\x99\xA9" /*63081, f669*/ +#define LV_SYMBOL_EXT_JEDI_ORDER "\xEF\x94\x8E" /*62734, f50e*/ +#define LV_SYMBOL_EXT_JENKINS "\xEF\x8E\xB6" /*62390, f3b6*/ +#define LV_SYMBOL_EXT_JET_FIGHTER "\xEF\x83\xBB" /*61691, f0fb*/ +#define LV_SYMBOL_EXT_JET_FIGHTER_UP "\xEE\x94\x98" /*58648, e518*/ +#define LV_SYMBOL_EXT_JIRA "\xEF\x9E\xB1" /*63409, f7b1*/ +#define LV_SYMBOL_EXT_JOGET "\xEF\x8E\xB7" /*62391, f3b7*/ +#define LV_SYMBOL_EXT_JOINT "\xEF\x96\x95" /*62869, f595*/ +#define LV_SYMBOL_EXT_JOOMLA "\xEF\x86\xAA" /*61866, f1aa*/ +#define LV_SYMBOL_EXT_JS "\xEF\x8E\xB8" /*62392, f3b8*/ +#define LV_SYMBOL_EXT_JSFIDDLE "\xEF\x87\x8C" /*61900, f1cc*/ +#define LV_SYMBOL_EXT_JUG_DETERGENT "\xEE\x94\x99" /*58649, e519*/ +#define LV_SYMBOL_EXT_K "\x4B" /*75, 4b*/ +#define LV_SYMBOL_EXT_KAABA "\xEF\x99\xAB" /*63083, f66b*/ +#define LV_SYMBOL_EXT_KAGGLE "\xEF\x97\xBA" /*62970, f5fa*/ +#define LV_SYMBOL_EXT_KEY "\xEF\x82\x84" /*61572, f084*/ +#define LV_SYMBOL_EXT_KEYBASE "\xEF\x93\xB5" /*62709, f4f5*/ +#define LV_SYMBOL_EXT_KEYBOARD "\xEF\x84\x9C" /*61724, f11c*/ +#define LV_SYMBOL_EXT_KEYCDN "\xEF\x8E\xBA" /*62394, f3ba*/ +#define LV_SYMBOL_EXT_KHANDA "\xEF\x99\xAD" /*63085, f66d*/ +#define LV_SYMBOL_EXT_KICKSTARTER "\xEF\x8E\xBB" /*62395, f3bb*/ +#define LV_SYMBOL_EXT_KICKSTARTER_K "\xEF\x8E\xBC" /*62396, f3bc*/ +#define LV_SYMBOL_EXT_KIP_SIGN "\xEE\x87\x84" /*57796, e1c4*/ +#define LV_SYMBOL_EXT_KIT_MEDICAL "\xEF\x91\xB9" /*62585, f479*/ +#define LV_SYMBOL_EXT_KITCHEN_SET "\xEE\x94\x9A" /*58650, e51a*/ +#define LV_SYMBOL_EXT_KIWI_BIRD "\xEF\x94\xB5" /*62773, f535*/ +#define LV_SYMBOL_EXT_KORVUE "\xEF\x90\xAF" /*62511, f42f*/ +#define LV_SYMBOL_EXT_L "\x4C" /*76, 4c*/ +#define LV_SYMBOL_EXT_LAND_MINE_ON "\xEE\x94\x9B" /*58651, e51b*/ +#define LV_SYMBOL_EXT_LANDMARK "\xEF\x99\xAF" /*63087, f66f*/ +#define LV_SYMBOL_EXT_LANDMARK_DOME "\xEF\x9D\x92" /*63314, f752*/ +#define LV_SYMBOL_EXT_LANDMARK_FLAG "\xEE\x94\x9C" /*58652, e51c*/ +#define LV_SYMBOL_EXT_LANGUAGE "\xEF\x86\xAB" /*61867, f1ab*/ +#define LV_SYMBOL_EXT_LAPTOP "\xEF\x84\x89" /*61705, f109*/ +#define LV_SYMBOL_EXT_LAPTOP_CODE "\xEF\x97\xBC" /*62972, f5fc*/ +#define LV_SYMBOL_EXT_LAPTOP_FILE "\xEE\x94\x9D" /*58653, e51d*/ +#define LV_SYMBOL_EXT_LAPTOP_MEDICAL "\xEF\xA0\x92" /*63506, f812*/ +#define LV_SYMBOL_EXT_LARAVEL "\xEF\x8E\xBD" /*62397, f3bd*/ +#define LV_SYMBOL_EXT_LARI_SIGN "\xEE\x87\x88" /*57800, e1c8*/ +#define LV_SYMBOL_EXT_LASTFM "\xEF\x88\x82" /*61954, f202*/ +#define LV_SYMBOL_EXT_LAYER_GROUP "\xEF\x97\xBD" /*62973, f5fd*/ +#define LV_SYMBOL_EXT_LEAF "\xEF\x81\xAC" /*61548, f06c*/ +#define LV_SYMBOL_EXT_LEANPUB "\xEF\x88\x92" /*61970, f212*/ +#define LV_SYMBOL_EXT_LEFT_LONG "\xEF\x8C\x8A" /*62218, f30a*/ +#define LV_SYMBOL_EXT_LEFT_RIGHT "\xEF\x8C\xB7" /*62263, f337*/ +#define LV_SYMBOL_EXT_LEMON "\xEF\x82\x94" /*61588, f094*/ +#define LV_SYMBOL_EXT_LESS "\xEF\x90\x9D" /*62493, f41d*/ +#define LV_SYMBOL_EXT_LESS_THAN "\x3C" /*60, 3c*/ +#define LV_SYMBOL_EXT_LESS_THAN_EQUAL "\xEF\x94\xB7" /*62775, f537*/ +#define LV_SYMBOL_EXT_LETTERBOXD "\xEE\x98\xAD" /*58925, e62d*/ +#define LV_SYMBOL_EXT_LIFE_RING "\xEF\x87\x8D" /*61901, f1cd*/ +#define LV_SYMBOL_EXT_LIGHTBULB "\xEF\x83\xAB" /*61675, f0eb*/ +#define LV_SYMBOL_EXT_LINE "\xEF\x8F\x80" /*62400, f3c0*/ +#define LV_SYMBOL_EXT_LINES_LEANING "\xEE\x94\x9E" /*58654, e51e*/ +#define LV_SYMBOL_EXT_LINK "\xEF\x83\x81" /*61633, f0c1*/ +#define LV_SYMBOL_EXT_LINK_SLASH "\xEF\x84\xA7" /*61735, f127*/ +#define LV_SYMBOL_EXT_LINKEDIN "\xEF\x82\x8C" /*61580, f08c*/ +#define LV_SYMBOL_EXT_LINKEDIN_IN "\xEF\x83\xA1" /*61665, f0e1*/ +#define LV_SYMBOL_EXT_LINODE "\xEF\x8A\xB8" /*62136, f2b8*/ +#define LV_SYMBOL_EXT_LINUX "\xEF\x85\xBC" /*61820, f17c*/ +#define LV_SYMBOL_EXT_LIRA_SIGN "\xEF\x86\x95" /*61845, f195*/ +#define LV_SYMBOL_EXT_LIST "\xEF\x80\xBA" /*61498, f03a*/ +#define LV_SYMBOL_EXT_LIST_CHECK "\xEF\x82\xAE" /*61614, f0ae*/ +#define LV_SYMBOL_EXT_LIST_OL "\xEF\x83\x8B" /*61643, f0cb*/ +#define LV_SYMBOL_EXT_LIST_UL "\xEF\x83\x8A" /*61642, f0ca*/ +#define LV_SYMBOL_EXT_LITECOIN_SIGN "\xEE\x87\x93" /*57811, e1d3*/ +#define LV_SYMBOL_EXT_LOCATION_ARROW "\xEF\x84\xA4" /*61732, f124*/ +#define LV_SYMBOL_EXT_LOCATION_CROSSHAIRS "\xEF\x98\x81" /*62977, f601*/ +#define LV_SYMBOL_EXT_LOCATION_DOT "\xEF\x8F\x85" /*62405, f3c5*/ +#define LV_SYMBOL_EXT_LOCATION_PIN "\xEF\x81\x81" /*61505, f041*/ +#define LV_SYMBOL_EXT_LOCATION_PIN_LOCK "\xEE\x94\x9F" /*58655, e51f*/ +#define LV_SYMBOL_EXT_LOCK "\xEF\x80\xA3" /*61475, f023*/ +#define LV_SYMBOL_EXT_LOCK_OPEN "\xEF\x8F\x81" /*62401, f3c1*/ +#define LV_SYMBOL_EXT_LOCUST "\xEE\x94\xA0" /*58656, e520*/ +#define LV_SYMBOL_EXT_LUNGS "\xEF\x98\x84" /*62980, f604*/ +#define LV_SYMBOL_EXT_LUNGS_VIRUS "\xEE\x81\xA7" /*57447, e067*/ +#define LV_SYMBOL_EXT_LYFT "\xEF\x8F\x83" /*62403, f3c3*/ +#define LV_SYMBOL_EXT_M "\x4D" /*77, 4d*/ +#define LV_SYMBOL_EXT_MAGENTO "\xEF\x8F\x84" /*62404, f3c4*/ +#define LV_SYMBOL_EXT_MAGNET "\xEF\x81\xB6" /*61558, f076*/ +#define LV_SYMBOL_EXT_MAGNIFYING_GLASS "\xEF\x80\x82" /*61442, f002*/ +#define LV_SYMBOL_EXT_MAGNIFYING_GLASS_ARROW_RIGHT "\xEE\x94\xA1" /*58657, e521*/ +#define LV_SYMBOL_EXT_MAGNIFYING_GLASS_CHART "\xEE\x94\xA2" /*58658, e522*/ +#define LV_SYMBOL_EXT_MAGNIFYING_GLASS_DOLLAR "\xEF\x9A\x88" /*63112, f688*/ +#define LV_SYMBOL_EXT_MAGNIFYING_GLASS_LOCATION "\xEF\x9A\x89" /*63113, f689*/ +#define LV_SYMBOL_EXT_MAGNIFYING_GLASS_MINUS "\xEF\x80\x90" /*61456, f010*/ +#define LV_SYMBOL_EXT_MAGNIFYING_GLASS_PLUS "\xEF\x80\x8E" /*61454, f00e*/ +#define LV_SYMBOL_EXT_MAILCHIMP "\xEF\x96\x9E" /*62878, f59e*/ +#define LV_SYMBOL_EXT_MANAT_SIGN "\xEE\x87\x95" /*57813, e1d5*/ +#define LV_SYMBOL_EXT_MANDALORIAN "\xEF\x94\x8F" /*62735, f50f*/ +#define LV_SYMBOL_EXT_MAP "\xEF\x89\xB9" /*62073, f279*/ +#define LV_SYMBOL_EXT_MAP_LOCATION "\xEF\x96\x9F" /*62879, f59f*/ +#define LV_SYMBOL_EXT_MAP_LOCATION_DOT "\xEF\x96\xA0" /*62880, f5a0*/ +#define LV_SYMBOL_EXT_MAP_PIN "\xEF\x89\xB6" /*62070, f276*/ +#define LV_SYMBOL_EXT_MARKDOWN "\xEF\x98\x8F" /*62991, f60f*/ +#define LV_SYMBOL_EXT_MARKER "\xEF\x96\xA1" /*62881, f5a1*/ +#define LV_SYMBOL_EXT_MARS "\xEF\x88\xA2" /*61986, f222*/ +#define LV_SYMBOL_EXT_MARS_AND_VENUS "\xEF\x88\xA4" /*61988, f224*/ +#define LV_SYMBOL_EXT_MARS_AND_VENUS_BURST "\xEE\x94\xA3" /*58659, e523*/ +#define LV_SYMBOL_EXT_MARS_DOUBLE "\xEF\x88\xA7" /*61991, f227*/ +#define LV_SYMBOL_EXT_MARS_STROKE "\xEF\x88\xA9" /*61993, f229*/ +#define LV_SYMBOL_EXT_MARS_STROKE_RIGHT "\xEF\x88\xAB" /*61995, f22b*/ +#define LV_SYMBOL_EXT_MARS_STROKE_UP "\xEF\x88\xAA" /*61994, f22a*/ +#define LV_SYMBOL_EXT_MARTINI_GLASS "\xEF\x95\xBB" /*62843, f57b*/ +#define LV_SYMBOL_EXT_MARTINI_GLASS_CITRUS "\xEF\x95\xA1" /*62817, f561*/ +#define LV_SYMBOL_EXT_MARTINI_GLASS_EMPTY "\xEF\x80\x80" /*61440, f000*/ +#define LV_SYMBOL_EXT_MASK "\xEF\x9B\xBA" /*63226, f6fa*/ +#define LV_SYMBOL_EXT_MASK_FACE "\xEE\x87\x97" /*57815, e1d7*/ +#define LV_SYMBOL_EXT_MASK_VENTILATOR "\xEE\x94\xA4" /*58660, e524*/ +#define LV_SYMBOL_EXT_MASKS_THEATER "\xEF\x98\xB0" /*63024, f630*/ +#define LV_SYMBOL_EXT_MASTODON "\xEF\x93\xB6" /*62710, f4f6*/ +#define LV_SYMBOL_EXT_MATTRESS_PILLOW "\xEE\x94\xA5" /*58661, e525*/ +#define LV_SYMBOL_EXT_MAXCDN "\xEF\x84\xB6" /*61750, f136*/ +#define LV_SYMBOL_EXT_MAXIMIZE "\xEF\x8C\x9E" /*62238, f31e*/ +#define LV_SYMBOL_EXT_MDB "\xEF\xA3\x8A" /*63690, f8ca*/ +#define LV_SYMBOL_EXT_MEDAL "\xEF\x96\xA2" /*62882, f5a2*/ +#define LV_SYMBOL_EXT_MEDAPPS "\xEF\x8F\x86" /*62406, f3c6*/ +#define LV_SYMBOL_EXT_MEDIUM "\xEF\x88\xBA" /*62010, f23a*/ +#define LV_SYMBOL_EXT_MEDRT "\xEF\x8F\x88" /*62408, f3c8*/ +#define LV_SYMBOL_EXT_MEETUP "\xEF\x8B\xA0" /*62176, f2e0*/ +#define LV_SYMBOL_EXT_MEGAPORT "\xEF\x96\xA3" /*62883, f5a3*/ +#define LV_SYMBOL_EXT_MEMORY "\xEF\x94\xB8" /*62776, f538*/ +#define LV_SYMBOL_EXT_MENDELEY "\xEF\x9E\xB3" /*63411, f7b3*/ +#define LV_SYMBOL_EXT_MENORAH "\xEF\x99\xB6" /*63094, f676*/ +#define LV_SYMBOL_EXT_MERCURY "\xEF\x88\xA3" /*61987, f223*/ +#define LV_SYMBOL_EXT_MESSAGE "\xEF\x89\xBA" /*62074, f27a*/ +#define LV_SYMBOL_EXT_META "\xEE\x92\x9B" /*58523, e49b*/ +#define LV_SYMBOL_EXT_METEOR "\xEF\x9D\x93" /*63315, f753*/ +#define LV_SYMBOL_EXT_MICROBLOG "\xEE\x80\x9A" /*57370, e01a*/ +#define LV_SYMBOL_EXT_MICROCHIP "\xEF\x8B\x9B" /*62171, f2db*/ +#define LV_SYMBOL_EXT_MICROPHONE "\xEF\x84\xB0" /*61744, f130*/ +#define LV_SYMBOL_EXT_MICROPHONE_LINES "\xEF\x8F\x89" /*62409, f3c9*/ +#define LV_SYMBOL_EXT_MICROPHONE_LINES_SLASH "\xEF\x94\xB9" /*62777, f539*/ +#define LV_SYMBOL_EXT_MICROPHONE_SLASH "\xEF\x84\xB1" /*61745, f131*/ +#define LV_SYMBOL_EXT_MICROSCOPE "\xEF\x98\x90" /*62992, f610*/ +#define LV_SYMBOL_EXT_MICROSOFT "\xEF\x8F\x8A" /*62410, f3ca*/ +#define LV_SYMBOL_EXT_MILL_SIGN "\xEE\x87\xAD" /*57837, e1ed*/ +#define LV_SYMBOL_EXT_MINIMIZE "\xEF\x9E\x8C" /*63372, f78c*/ +#define LV_SYMBOL_EXT_MINTBIT "\xEE\x98\xAF" /*58927, e62f*/ +#define LV_SYMBOL_EXT_MINUS "\xEF\x81\xA8" /*61544, f068*/ +#define LV_SYMBOL_EXT_MITTEN "\xEF\x9E\xB5" /*63413, f7b5*/ +#define LV_SYMBOL_EXT_MIX "\xEF\x8F\x8B" /*62411, f3cb*/ +#define LV_SYMBOL_EXT_MIXCLOUD "\xEF\x8A\x89" /*62089, f289*/ +#define LV_SYMBOL_EXT_MIXER "\xEE\x81\x96" /*57430, e056*/ +#define LV_SYMBOL_EXT_MIZUNI "\xEF\x8F\x8C" /*62412, f3cc*/ +#define LV_SYMBOL_EXT_MOBILE "\xEF\x8F\x8E" /*62414, f3ce*/ +#define LV_SYMBOL_EXT_MOBILE_BUTTON "\xEF\x84\x8B" /*61707, f10b*/ +#define LV_SYMBOL_EXT_MOBILE_RETRO "\xEE\x94\xA7" /*58663, e527*/ +#define LV_SYMBOL_EXT_MOBILE_SCREEN "\xEF\x8F\x8F" /*62415, f3cf*/ +#define LV_SYMBOL_EXT_MOBILE_SCREEN_BUTTON "\xEF\x8F\x8D" /*62413, f3cd*/ +#define LV_SYMBOL_EXT_MODX "\xEF\x8A\x85" /*62085, f285*/ +#define LV_SYMBOL_EXT_MONERO "\xEF\x8F\x90" /*62416, f3d0*/ +#define LV_SYMBOL_EXT_MONEY_BILL "\xEF\x83\x96" /*61654, f0d6*/ +#define LV_SYMBOL_EXT_MONEY_BILL_1 "\xEF\x8F\x91" /*62417, f3d1*/ +#define LV_SYMBOL_EXT_MONEY_BILL_1_WAVE "\xEF\x94\xBB" /*62779, f53b*/ +#define LV_SYMBOL_EXT_MONEY_BILL_TRANSFER "\xEE\x94\xA8" /*58664, e528*/ +#define LV_SYMBOL_EXT_MONEY_BILL_TREND_UP "\xEE\x94\xA9" /*58665, e529*/ +#define LV_SYMBOL_EXT_MONEY_BILL_WAVE "\xEF\x94\xBA" /*62778, f53a*/ +#define LV_SYMBOL_EXT_MONEY_BILL_WHEAT "\xEE\x94\xAA" /*58666, e52a*/ +#define LV_SYMBOL_EXT_MONEY_BILLS "\xEE\x87\xB3" /*57843, e1f3*/ +#define LV_SYMBOL_EXT_MONEY_CHECK "\xEF\x94\xBC" /*62780, f53c*/ +#define LV_SYMBOL_EXT_MONEY_CHECK_DOLLAR "\xEF\x94\xBD" /*62781, f53d*/ +#define LV_SYMBOL_EXT_MONUMENT "\xEF\x96\xA6" /*62886, f5a6*/ +#define LV_SYMBOL_EXT_MOON "\xEF\x86\x86" /*61830, f186*/ +#define LV_SYMBOL_EXT_MORTAR_PESTLE "\xEF\x96\xA7" /*62887, f5a7*/ +#define LV_SYMBOL_EXT_MOSQUE "\xEF\x99\xB8" /*63096, f678*/ +#define LV_SYMBOL_EXT_MOSQUITO "\xEE\x94\xAB" /*58667, e52b*/ +#define LV_SYMBOL_EXT_MOSQUITO_NET "\xEE\x94\xAC" /*58668, e52c*/ +#define LV_SYMBOL_EXT_MOTORCYCLE "\xEF\x88\x9C" /*61980, f21c*/ +#define LV_SYMBOL_EXT_MOUND "\xEE\x94\xAD" /*58669, e52d*/ +#define LV_SYMBOL_EXT_MOUNTAIN "\xEF\x9B\xBC" /*63228, f6fc*/ +#define LV_SYMBOL_EXT_MOUNTAIN_CITY "\xEE\x94\xAE" /*58670, e52e*/ +#define LV_SYMBOL_EXT_MOUNTAIN_SUN "\xEE\x94\xAF" /*58671, e52f*/ +#define LV_SYMBOL_EXT_MUG_HOT "\xEF\x9E\xB6" /*63414, f7b6*/ +#define LV_SYMBOL_EXT_MUG_SAUCER "\xEF\x83\xB4" /*61684, f0f4*/ +#define LV_SYMBOL_EXT_MUSIC "\xEF\x80\x81" /*61441, f001*/ +#define LV_SYMBOL_EXT_N "\x4E" /*78, 4e*/ +#define LV_SYMBOL_EXT_NAIRA_SIGN "\xEE\x87\xB6" /*57846, e1f6*/ +#define LV_SYMBOL_EXT_NAPSTER "\xEF\x8F\x92" /*62418, f3d2*/ +#define LV_SYMBOL_EXT_NEOS "\xEF\x98\x92" /*62994, f612*/ +#define LV_SYMBOL_EXT_NETWORK_WIRED "\xEF\x9B\xBF" /*63231, f6ff*/ +#define LV_SYMBOL_EXT_NEUTER "\xEF\x88\xAC" /*61996, f22c*/ +#define LV_SYMBOL_EXT_NEWSPAPER "\xEF\x87\xAA" /*61930, f1ea*/ +#define LV_SYMBOL_EXT_NFC_DIRECTIONAL "\xEE\x94\xB0" /*58672, e530*/ +#define LV_SYMBOL_EXT_NFC_SYMBOL "\xEE\x94\xB1" /*58673, e531*/ +#define LV_SYMBOL_EXT_NIMBLR "\xEF\x96\xA8" /*62888, f5a8*/ +#define LV_SYMBOL_EXT_NODE "\xEF\x90\x99" /*62489, f419*/ +#define LV_SYMBOL_EXT_NODE_JS "\xEF\x8F\x93" /*62419, f3d3*/ +#define LV_SYMBOL_EXT_NOT_EQUAL "\xEF\x94\xBE" /*62782, f53e*/ +#define LV_SYMBOL_EXT_NOTDEF "\xEE\x87\xBE" /*57854, e1fe*/ +#define LV_SYMBOL_EXT_NOTE_STICKY "\xEF\x89\x89" /*62025, f249*/ +#define LV_SYMBOL_EXT_NOTES_MEDICAL "\xEF\x92\x81" /*62593, f481*/ +#define LV_SYMBOL_EXT_NPM "\xEF\x8F\x94" /*62420, f3d4*/ +#define LV_SYMBOL_EXT_NS8 "\xEF\x8F\x95" /*62421, f3d5*/ +#define LV_SYMBOL_EXT_NUTRITIONIX "\xEF\x8F\x96" /*62422, f3d6*/ +#define LV_SYMBOL_EXT_O "\x4F" /*79, 4f*/ +#define LV_SYMBOL_EXT_OBJECT_GROUP "\xEF\x89\x87" /*62023, f247*/ +#define LV_SYMBOL_EXT_OBJECT_UNGROUP "\xEF\x89\x88" /*62024, f248*/ +#define LV_SYMBOL_EXT_OCTOPUS_DEPLOY "\xEE\x82\x82" /*57474, e082*/ +#define LV_SYMBOL_EXT_ODNOKLASSNIKI "\xEF\x89\xA3" /*62051, f263*/ +#define LV_SYMBOL_EXT_ODYSEE "\xEE\x97\x86" /*58822, e5c6*/ +#define LV_SYMBOL_EXT_OIL_CAN "\xEF\x98\x93" /*62995, f613*/ +#define LV_SYMBOL_EXT_OIL_WELL "\xEE\x94\xB2" /*58674, e532*/ +#define LV_SYMBOL_EXT_OLD_REPUBLIC "\xEF\x94\x90" /*62736, f510*/ +#define LV_SYMBOL_EXT_OM "\xEF\x99\xB9" /*63097, f679*/ +#define LV_SYMBOL_EXT_OPENCART "\xEF\x88\xBD" /*62013, f23d*/ +#define LV_SYMBOL_EXT_OPENID "\xEF\x86\x9B" /*61851, f19b*/ +#define LV_SYMBOL_EXT_OPENSUSE "\xEE\x98\xAB" /*58923, e62b*/ +#define LV_SYMBOL_EXT_OPERA "\xEF\x89\xAA" /*62058, f26a*/ +#define LV_SYMBOL_EXT_OPTIN_MONSTER "\xEF\x88\xBC" /*62012, f23c*/ +#define LV_SYMBOL_EXT_ORCID "\xEF\xA3\x92" /*63698, f8d2*/ +#define LV_SYMBOL_EXT_OSI "\xEF\x90\x9A" /*62490, f41a*/ +#define LV_SYMBOL_EXT_OTTER "\xEF\x9C\x80" /*63232, f700*/ +#define LV_SYMBOL_EXT_OUTDENT "\xEF\x80\xBB" /*61499, f03b*/ +#define LV_SYMBOL_EXT_P "\x50" /*80, 50*/ +#define LV_SYMBOL_EXT_PADLET "\xEE\x92\xA0" /*58528, e4a0*/ +#define LV_SYMBOL_EXT_PAGE4 "\xEF\x8F\x97" /*62423, f3d7*/ +#define LV_SYMBOL_EXT_PAGELINES "\xEF\x86\x8C" /*61836, f18c*/ +#define LV_SYMBOL_EXT_PAGER "\xEF\xA0\x95" /*63509, f815*/ +#define LV_SYMBOL_EXT_PAINT_ROLLER "\xEF\x96\xAA" /*62890, f5aa*/ +#define LV_SYMBOL_EXT_PAINTBRUSH "\xEF\x87\xBC" /*61948, f1fc*/ +#define LV_SYMBOL_EXT_PALETTE "\xEF\x94\xBF" /*62783, f53f*/ +#define LV_SYMBOL_EXT_PALFED "\xEF\x8F\x98" /*62424, f3d8*/ +#define LV_SYMBOL_EXT_PALLET "\xEF\x92\x82" /*62594, f482*/ +#define LV_SYMBOL_EXT_PANORAMA "\xEE\x88\x89" /*57865, e209*/ +#define LV_SYMBOL_EXT_PAPER_PLANE "\xEF\x87\x98" /*61912, f1d8*/ +#define LV_SYMBOL_EXT_PAPERCLIP "\xEF\x83\x86" /*61638, f0c6*/ +#define LV_SYMBOL_EXT_PARACHUTE_BOX "\xEF\x93\x8D" /*62669, f4cd*/ +#define LV_SYMBOL_EXT_PARAGRAPH "\xEF\x87\x9D" /*61917, f1dd*/ +#define LV_SYMBOL_EXT_PASSPORT "\xEF\x96\xAB" /*62891, f5ab*/ +#define LV_SYMBOL_EXT_PASTE "\xEF\x83\xAA" /*61674, f0ea*/ +#define LV_SYMBOL_EXT_PATREON "\xEF\x8F\x99" /*62425, f3d9*/ +#define LV_SYMBOL_EXT_PAUSE "\xEF\x81\x8C" /*61516, f04c*/ +#define LV_SYMBOL_EXT_PAW "\xEF\x86\xB0" /*61872, f1b0*/ +#define LV_SYMBOL_EXT_PAYPAL "\xEF\x87\xAD" /*61933, f1ed*/ +#define LV_SYMBOL_EXT_PEACE "\xEF\x99\xBC" /*63100, f67c*/ +#define LV_SYMBOL_EXT_PEN "\xEF\x8C\x84" /*62212, f304*/ +#define LV_SYMBOL_EXT_PEN_CLIP "\xEF\x8C\x85" /*62213, f305*/ +#define LV_SYMBOL_EXT_PEN_FANCY "\xEF\x96\xAC" /*62892, f5ac*/ +#define LV_SYMBOL_EXT_PEN_NIB "\xEF\x96\xAD" /*62893, f5ad*/ +#define LV_SYMBOL_EXT_PEN_RULER "\xEF\x96\xAE" /*62894, f5ae*/ +#define LV_SYMBOL_EXT_PEN_TO_SQUARE "\xEF\x81\x84" /*61508, f044*/ +#define LV_SYMBOL_EXT_PENCIL "\xEF\x8C\x83" /*62211, f303*/ +#define LV_SYMBOL_EXT_PEOPLE_ARROWS "\xEE\x81\xA8" /*57448, e068*/ +#define LV_SYMBOL_EXT_PEOPLE_CARRY_BOX "\xEF\x93\x8E" /*62670, f4ce*/ +#define LV_SYMBOL_EXT_PEOPLE_GROUP "\xEE\x94\xB3" /*58675, e533*/ +#define LV_SYMBOL_EXT_PEOPLE_LINE "\xEE\x94\xB4" /*58676, e534*/ +#define LV_SYMBOL_EXT_PEOPLE_PULLING "\xEE\x94\xB5" /*58677, e535*/ +#define LV_SYMBOL_EXT_PEOPLE_ROBBERY "\xEE\x94\xB6" /*58678, e536*/ +#define LV_SYMBOL_EXT_PEOPLE_ROOF "\xEE\x94\xB7" /*58679, e537*/ +#define LV_SYMBOL_EXT_PEPPER_HOT "\xEF\xA0\x96" /*63510, f816*/ +#define LV_SYMBOL_EXT_PERBYTE "\xEE\x82\x83" /*57475, e083*/ +#define LV_SYMBOL_EXT_PERCENT "\x25" /*37, 25*/ +#define LV_SYMBOL_EXT_PERISCOPE "\xEF\x8F\x9A" /*62426, f3da*/ +#define LV_SYMBOL_EXT_PERSON "\xEF\x86\x83" /*61827, f183*/ +#define LV_SYMBOL_EXT_PERSON_ARROW_DOWN_TO_LINE "\xEE\x94\xB8" /*58680, e538*/ +#define LV_SYMBOL_EXT_PERSON_ARROW_UP_FROM_LINE "\xEE\x94\xB9" /*58681, e539*/ +#define LV_SYMBOL_EXT_PERSON_BIKING "\xEF\xA1\x8A" /*63562, f84a*/ +#define LV_SYMBOL_EXT_PERSON_BOOTH "\xEF\x9D\x96" /*63318, f756*/ +#define LV_SYMBOL_EXT_PERSON_BREASTFEEDING "\xEE\x94\xBA" /*58682, e53a*/ +#define LV_SYMBOL_EXT_PERSON_BURST "\xEE\x94\xBB" /*58683, e53b*/ +#define LV_SYMBOL_EXT_PERSON_CANE "\xEE\x94\xBC" /*58684, e53c*/ +#define LV_SYMBOL_EXT_PERSON_CHALKBOARD "\xEE\x94\xBD" /*58685, e53d*/ +#define LV_SYMBOL_EXT_PERSON_CIRCLE_CHECK "\xEE\x94\xBE" /*58686, e53e*/ +#define LV_SYMBOL_EXT_PERSON_CIRCLE_EXCLAMATION "\xEE\x94\xBF" /*58687, e53f*/ +#define LV_SYMBOL_EXT_PERSON_CIRCLE_MINUS "\xEE\x95\x80" /*58688, e540*/ +#define LV_SYMBOL_EXT_PERSON_CIRCLE_PLUS "\xEE\x95\x81" /*58689, e541*/ +#define LV_SYMBOL_EXT_PERSON_CIRCLE_QUESTION "\xEE\x95\x82" /*58690, e542*/ +#define LV_SYMBOL_EXT_PERSON_CIRCLE_XMARK "\xEE\x95\x83" /*58691, e543*/ +#define LV_SYMBOL_EXT_PERSON_DIGGING "\xEF\xA1\x9E" /*63582, f85e*/ +#define LV_SYMBOL_EXT_PERSON_DOTS_FROM_LINE "\xEF\x91\xB0" /*62576, f470*/ +#define LV_SYMBOL_EXT_PERSON_DRESS "\xEF\x86\x82" /*61826, f182*/ +#define LV_SYMBOL_EXT_PERSON_DRESS_BURST "\xEE\x95\x84" /*58692, e544*/ +#define LV_SYMBOL_EXT_PERSON_DROWNING "\xEE\x95\x85" /*58693, e545*/ +#define LV_SYMBOL_EXT_PERSON_FALLING "\xEE\x95\x86" /*58694, e546*/ +#define LV_SYMBOL_EXT_PERSON_FALLING_BURST "\xEE\x95\x87" /*58695, e547*/ +#define LV_SYMBOL_EXT_PERSON_HALF_DRESS "\xEE\x95\x88" /*58696, e548*/ +#define LV_SYMBOL_EXT_PERSON_HARASSING "\xEE\x95\x89" /*58697, e549*/ +#define LV_SYMBOL_EXT_PERSON_HIKING "\xEF\x9B\xAC" /*63212, f6ec*/ +#define LV_SYMBOL_EXT_PERSON_MILITARY_POINTING "\xEE\x95\x8A" /*58698, e54a*/ +#define LV_SYMBOL_EXT_PERSON_MILITARY_RIFLE "\xEE\x95\x8B" /*58699, e54b*/ +#define LV_SYMBOL_EXT_PERSON_MILITARY_TO_PERSON "\xEE\x95\x8C" /*58700, e54c*/ +#define LV_SYMBOL_EXT_PERSON_PRAYING "\xEF\x9A\x83" /*63107, f683*/ +#define LV_SYMBOL_EXT_PERSON_PREGNANT "\xEE\x8C\x9E" /*58142, e31e*/ +#define LV_SYMBOL_EXT_PERSON_RAYS "\xEE\x95\x8D" /*58701, e54d*/ +#define LV_SYMBOL_EXT_PERSON_RIFLE "\xEE\x95\x8E" /*58702, e54e*/ +#define LV_SYMBOL_EXT_PERSON_RUNNING "\xEF\x9C\x8C" /*63244, f70c*/ +#define LV_SYMBOL_EXT_PERSON_SHELTER "\xEE\x95\x8F" /*58703, e54f*/ +#define LV_SYMBOL_EXT_PERSON_SKATING "\xEF\x9F\x85" /*63429, f7c5*/ +#define LV_SYMBOL_EXT_PERSON_SKIING "\xEF\x9F\x89" /*63433, f7c9*/ +#define LV_SYMBOL_EXT_PERSON_SKIING_NORDIC "\xEF\x9F\x8A" /*63434, f7ca*/ +#define LV_SYMBOL_EXT_PERSON_SNOWBOARDING "\xEF\x9F\x8E" /*63438, f7ce*/ +#define LV_SYMBOL_EXT_PERSON_SWIMMING "\xEF\x97\x84" /*62916, f5c4*/ +#define LV_SYMBOL_EXT_PERSON_THROUGH_WINDOW "\xEE\x96\xA9" /*58793, e5a9*/ +#define LV_SYMBOL_EXT_PERSON_WALKING "\xEF\x95\x94" /*62804, f554*/ +#define LV_SYMBOL_EXT_PERSON_WALKING_ARROW_LOOP_LEFT "\xEE\x95\x91" /*58705, e551*/ +#define LV_SYMBOL_EXT_PERSON_WALKING_ARROW_RIGHT "\xEE\x95\x92" /*58706, e552*/ +#define LV_SYMBOL_EXT_PERSON_WALKING_DASHED_LINE_ARROW_RIGHT "\xEE\x95\x93" /*58707, e553*/ +#define LV_SYMBOL_EXT_PERSON_WALKING_LUGGAGE "\xEE\x95\x94" /*58708, e554*/ +#define LV_SYMBOL_EXT_PERSON_WALKING_WITH_CANE "\xEF\x8A\x9D" /*62109, f29d*/ +#define LV_SYMBOL_EXT_PESETA_SIGN "\xEE\x88\xA1" /*57889, e221*/ +#define LV_SYMBOL_EXT_PESO_SIGN "\xEE\x88\xA2" /*57890, e222*/ +#define LV_SYMBOL_EXT_PHABRICATOR "\xEF\x8F\x9B" /*62427, f3db*/ +#define LV_SYMBOL_EXT_PHOENIX_FRAMEWORK "\xEF\x8F\x9C" /*62428, f3dc*/ +#define LV_SYMBOL_EXT_PHOENIX_SQUADRON "\xEF\x94\x91" /*62737, f511*/ +#define LV_SYMBOL_EXT_PHONE "\xEF\x82\x95" /*61589, f095*/ +#define LV_SYMBOL_EXT_PHONE_FLIP "\xEF\xA1\xB9" /*63609, f879*/ +#define LV_SYMBOL_EXT_PHONE_SLASH "\xEF\x8F\x9D" /*62429, f3dd*/ +#define LV_SYMBOL_EXT_PHONE_VOLUME "\xEF\x8A\xA0" /*62112, f2a0*/ +#define LV_SYMBOL_EXT_PHOTO_FILM "\xEF\xA1\xBC" /*63612, f87c*/ +#define LV_SYMBOL_EXT_PHP "\xEF\x91\x97" /*62551, f457*/ +#define LV_SYMBOL_EXT_PIED_PIPER "\xEF\x8A\xAE" /*62126, f2ae*/ +#define LV_SYMBOL_EXT_PIED_PIPER_ALT "\xEF\x86\xA8" /*61864, f1a8*/ +#define LV_SYMBOL_EXT_PIED_PIPER_HAT "\xEF\x93\xA5" /*62693, f4e5*/ +#define LV_SYMBOL_EXT_PIED_PIPER_PP "\xEF\x86\xA7" /*61863, f1a7*/ +#define LV_SYMBOL_EXT_PIGGY_BANK "\xEF\x93\x93" /*62675, f4d3*/ +#define LV_SYMBOL_EXT_PILLS "\xEF\x92\x84" /*62596, f484*/ +#define LV_SYMBOL_EXT_PINTEREST "\xEF\x83\x92" /*61650, f0d2*/ +#define LV_SYMBOL_EXT_PINTEREST_P "\xEF\x88\xB1" /*62001, f231*/ +#define LV_SYMBOL_EXT_PIX "\xEE\x90\xBA" /*58426, e43a*/ +#define LV_SYMBOL_EXT_PIXIV "\xEE\x99\x80" /*58944, e640*/ +#define LV_SYMBOL_EXT_PIZZA_SLICE "\xEF\xA0\x98" /*63512, f818*/ +#define LV_SYMBOL_EXT_PLACE_OF_WORSHIP "\xEF\x99\xBF" /*63103, f67f*/ +#define LV_SYMBOL_EXT_PLANE "\xEF\x81\xB2" /*61554, f072*/ +#define LV_SYMBOL_EXT_PLANE_ARRIVAL "\xEF\x96\xAF" /*62895, f5af*/ +#define LV_SYMBOL_EXT_PLANE_CIRCLE_CHECK "\xEE\x95\x95" /*58709, e555*/ +#define LV_SYMBOL_EXT_PLANE_CIRCLE_EXCLAMATION "\xEE\x95\x96" /*58710, e556*/ +#define LV_SYMBOL_EXT_PLANE_CIRCLE_XMARK "\xEE\x95\x97" /*58711, e557*/ +#define LV_SYMBOL_EXT_PLANE_DEPARTURE "\xEF\x96\xB0" /*62896, f5b0*/ +#define LV_SYMBOL_EXT_PLANE_LOCK "\xEE\x95\x98" /*58712, e558*/ +#define LV_SYMBOL_EXT_PLANE_SLASH "\xEE\x81\xA9" /*57449, e069*/ +#define LV_SYMBOL_EXT_PLANE_UP "\xEE\x88\xAD" /*57901, e22d*/ +#define LV_SYMBOL_EXT_PLANT_WILT "\xEE\x96\xAA" /*58794, e5aa*/ +#define LV_SYMBOL_EXT_PLATE_WHEAT "\xEE\x95\x9A" /*58714, e55a*/ +#define LV_SYMBOL_EXT_PLAY "\xEF\x81\x8B" /*61515, f04b*/ +#define LV_SYMBOL_EXT_PLAYSTATION "\xEF\x8F\x9F" /*62431, f3df*/ +#define LV_SYMBOL_EXT_PLUG "\xEF\x87\xA6" /*61926, f1e6*/ +#define LV_SYMBOL_EXT_PLUG_CIRCLE_BOLT "\xEE\x95\x9B" /*58715, e55b*/ +#define LV_SYMBOL_EXT_PLUG_CIRCLE_CHECK "\xEE\x95\x9C" /*58716, e55c*/ +#define LV_SYMBOL_EXT_PLUG_CIRCLE_EXCLAMATION "\xEE\x95\x9D" /*58717, e55d*/ +#define LV_SYMBOL_EXT_PLUG_CIRCLE_MINUS "\xEE\x95\x9E" /*58718, e55e*/ +#define LV_SYMBOL_EXT_PLUG_CIRCLE_PLUS "\xEE\x95\x9F" /*58719, e55f*/ +#define LV_SYMBOL_EXT_PLUG_CIRCLE_XMARK "\xEE\x95\xA0" /*58720, e560*/ +#define LV_SYMBOL_EXT_PLUS "\x2B" /*43, 2b*/ +#define LV_SYMBOL_EXT_PLUS_MINUS "\xEE\x90\xBC" /*58428, e43c*/ +#define LV_SYMBOL_EXT_PODCAST "\xEF\x8B\x8E" /*62158, f2ce*/ +#define LV_SYMBOL_EXT_POO "\xEF\x8B\xBE" /*62206, f2fe*/ +#define LV_SYMBOL_EXT_POO_STORM "\xEF\x9D\x9A" /*63322, f75a*/ +#define LV_SYMBOL_EXT_POOP "\xEF\x98\x99" /*63001, f619*/ +#define LV_SYMBOL_EXT_POWER_OFF "\xEF\x80\x91" /*61457, f011*/ +#define LV_SYMBOL_EXT_PRESCRIPTION "\xEF\x96\xB1" /*62897, f5b1*/ +#define LV_SYMBOL_EXT_PRESCRIPTION_BOTTLE "\xEF\x92\x85" /*62597, f485*/ +#define LV_SYMBOL_EXT_PRESCRIPTION_BOTTLE_MEDICAL "\xEF\x92\x86" /*62598, f486*/ +#define LV_SYMBOL_EXT_PRINT "\xEF\x80\xAF" /*61487, f02f*/ +#define LV_SYMBOL_EXT_PRODUCT_HUNT "\xEF\x8A\x88" /*62088, f288*/ +#define LV_SYMBOL_EXT_PUMP_MEDICAL "\xEE\x81\xAA" /*57450, e06a*/ +#define LV_SYMBOL_EXT_PUMP_SOAP "\xEE\x81\xAB" /*57451, e06b*/ +#define LV_SYMBOL_EXT_PUSHED "\xEF\x8F\xA1" /*62433, f3e1*/ +#define LV_SYMBOL_EXT_PUZZLE_PIECE "\xEF\x84\xAE" /*61742, f12e*/ +#define LV_SYMBOL_EXT_PYTHON "\xEF\x8F\xA2" /*62434, f3e2*/ +#define LV_SYMBOL_EXT_Q "\x51" /*81, 51*/ +#define LV_SYMBOL_EXT_QQ "\xEF\x87\x96" /*61910, f1d6*/ +#define LV_SYMBOL_EXT_QRCODE "\xEF\x80\xA9" /*61481, f029*/ +#define LV_SYMBOL_EXT_QUESTION "\x3F" /*63, 3f*/ +#define LV_SYMBOL_EXT_QUINSCAPE "\xEF\x91\x99" /*62553, f459*/ +#define LV_SYMBOL_EXT_QUORA "\xEF\x8B\x84" /*62148, f2c4*/ +#define LV_SYMBOL_EXT_QUOTE_LEFT "\xEF\x84\x8D" /*61709, f10d*/ +#define LV_SYMBOL_EXT_QUOTE_RIGHT "\xEF\x84\x8E" /*61710, f10e*/ +#define LV_SYMBOL_EXT_R "\x52" /*82, 52*/ +#define LV_SYMBOL_EXT_R_PROJECT "\xEF\x93\xB7" /*62711, f4f7*/ +#define LV_SYMBOL_EXT_RADIATION "\xEF\x9E\xB9" /*63417, f7b9*/ +#define LV_SYMBOL_EXT_RADIO "\xEF\xA3\x97" /*63703, f8d7*/ +#define LV_SYMBOL_EXT_RAINBOW "\xEF\x9D\x9B" /*63323, f75b*/ +#define LV_SYMBOL_EXT_RANKING_STAR "\xEE\x95\xA1" /*58721, e561*/ +#define LV_SYMBOL_EXT_RASPBERRY_PI "\xEF\x9E\xBB" /*63419, f7bb*/ +#define LV_SYMBOL_EXT_RAVELRY "\xEF\x8B\x99" /*62169, f2d9*/ +#define LV_SYMBOL_EXT_REACT "\xEF\x90\x9B" /*62491, f41b*/ +#define LV_SYMBOL_EXT_REACTEUROPE "\xEF\x9D\x9D" /*63325, f75d*/ +#define LV_SYMBOL_EXT_README "\xEF\x93\x95" /*62677, f4d5*/ +#define LV_SYMBOL_EXT_REBEL "\xEF\x87\x90" /*61904, f1d0*/ +#define LV_SYMBOL_EXT_RECEIPT "\xEF\x95\x83" /*62787, f543*/ +#define LV_SYMBOL_EXT_RECORD_VINYL "\xEF\xA3\x99" /*63705, f8d9*/ +#define LV_SYMBOL_EXT_RECTANGLE_AD "\xEF\x99\x81" /*63041, f641*/ +#define LV_SYMBOL_EXT_RECTANGLE_LIST "\xEF\x80\xA2" /*61474, f022*/ +#define LV_SYMBOL_EXT_RECTANGLE_XMARK "\xEF\x90\x90" /*62480, f410*/ +#define LV_SYMBOL_EXT_RECYCLE "\xEF\x86\xB8" /*61880, f1b8*/ +#define LV_SYMBOL_EXT_RED_RIVER "\xEF\x8F\xA3" /*62435, f3e3*/ +#define LV_SYMBOL_EXT_REDDIT "\xEF\x86\xA1" /*61857, f1a1*/ +#define LV_SYMBOL_EXT_REDDIT_ALIEN "\xEF\x8A\x81" /*62081, f281*/ +#define LV_SYMBOL_EXT_REDHAT "\xEF\x9E\xBC" /*63420, f7bc*/ +#define LV_SYMBOL_EXT_REGISTERED "\xEF\x89\x9D" /*62045, f25d*/ +#define LV_SYMBOL_EXT_RENREN "\xEF\x86\x8B" /*61835, f18b*/ +#define LV_SYMBOL_EXT_REPEAT "\xEF\x8D\xA3" /*62307, f363*/ +#define LV_SYMBOL_EXT_REPLY "\xEF\x8F\xA5" /*62437, f3e5*/ +#define LV_SYMBOL_EXT_REPLY_ALL "\xEF\x84\xA2" /*61730, f122*/ +#define LV_SYMBOL_EXT_REPLYD "\xEF\x8F\xA6" /*62438, f3e6*/ +#define LV_SYMBOL_EXT_REPUBLICAN "\xEF\x9D\x9E" /*63326, f75e*/ +#define LV_SYMBOL_EXT_RESEARCHGATE "\xEF\x93\xB8" /*62712, f4f8*/ +#define LV_SYMBOL_EXT_RESOLVING "\xEF\x8F\xA7" /*62439, f3e7*/ +#define LV_SYMBOL_EXT_RESTROOM "\xEF\x9E\xBD" /*63421, f7bd*/ +#define LV_SYMBOL_EXT_RETWEET "\xEF\x81\xB9" /*61561, f079*/ +#define LV_SYMBOL_EXT_REV "\xEF\x96\xB2" /*62898, f5b2*/ +#define LV_SYMBOL_EXT_RIBBON "\xEF\x93\x96" /*62678, f4d6*/ +#define LV_SYMBOL_EXT_RIGHT_FROM_BRACKET "\xEF\x8B\xB5" /*62197, f2f5*/ +#define LV_SYMBOL_EXT_RIGHT_LEFT "\xEF\x8D\xA2" /*62306, f362*/ +#define LV_SYMBOL_EXT_RIGHT_LONG "\xEF\x8C\x8B" /*62219, f30b*/ +#define LV_SYMBOL_EXT_RIGHT_TO_BRACKET "\xEF\x8B\xB6" /*62198, f2f6*/ +#define LV_SYMBOL_EXT_RING "\xEF\x9C\x8B" /*63243, f70b*/ +#define LV_SYMBOL_EXT_ROAD "\xEF\x80\x98" /*61464, f018*/ +#define LV_SYMBOL_EXT_ROAD_BARRIER "\xEE\x95\xA2" /*58722, e562*/ +#define LV_SYMBOL_EXT_ROAD_BRIDGE "\xEE\x95\xA3" /*58723, e563*/ +#define LV_SYMBOL_EXT_ROAD_CIRCLE_CHECK "\xEE\x95\xA4" /*58724, e564*/ +#define LV_SYMBOL_EXT_ROAD_CIRCLE_EXCLAMATION "\xEE\x95\xA5" /*58725, e565*/ +#define LV_SYMBOL_EXT_ROAD_CIRCLE_XMARK "\xEE\x95\xA6" /*58726, e566*/ +#define LV_SYMBOL_EXT_ROAD_LOCK "\xEE\x95\xA7" /*58727, e567*/ +#define LV_SYMBOL_EXT_ROAD_SPIKES "\xEE\x95\xA8" /*58728, e568*/ +#define LV_SYMBOL_EXT_ROBOT "\xEF\x95\x84" /*62788, f544*/ +#define LV_SYMBOL_EXT_ROCKET "\xEF\x84\xB5" /*61749, f135*/ +#define LV_SYMBOL_EXT_ROCKETCHAT "\xEF\x8F\xA8" /*62440, f3e8*/ +#define LV_SYMBOL_EXT_ROCKRMS "\xEF\x8F\xA9" /*62441, f3e9*/ +#define LV_SYMBOL_EXT_ROTATE "\xEF\x8B\xB1" /*62193, f2f1*/ +#define LV_SYMBOL_EXT_ROTATE_LEFT "\xEF\x8B\xAA" /*62186, f2ea*/ +#define LV_SYMBOL_EXT_ROTATE_RIGHT "\xEF\x8B\xB9" /*62201, f2f9*/ +#define LV_SYMBOL_EXT_ROUTE "\xEF\x93\x97" /*62679, f4d7*/ +#define LV_SYMBOL_EXT_RSS "\xEF\x82\x9E" /*61598, f09e*/ +#define LV_SYMBOL_EXT_RUBLE_SIGN "\xEF\x85\x98" /*61784, f158*/ +#define LV_SYMBOL_EXT_RUG "\xEE\x95\xA9" /*58729, e569*/ +#define LV_SYMBOL_EXT_RULER "\xEF\x95\x85" /*62789, f545*/ +#define LV_SYMBOL_EXT_RULER_COMBINED "\xEF\x95\x86" /*62790, f546*/ +#define LV_SYMBOL_EXT_RULER_HORIZONTAL "\xEF\x95\x87" /*62791, f547*/ +#define LV_SYMBOL_EXT_RULER_VERTICAL "\xEF\x95\x88" /*62792, f548*/ +#define LV_SYMBOL_EXT_RUPEE_SIGN "\xEF\x85\x96" /*61782, f156*/ +#define LV_SYMBOL_EXT_RUPIAH_SIGN "\xEE\x88\xBD" /*57917, e23d*/ +#define LV_SYMBOL_EXT_RUST "\xEE\x81\xBA" /*57466, e07a*/ +#define LV_SYMBOL_EXT_S "\x53" /*83, 53*/ +#define LV_SYMBOL_EXT_SACK_DOLLAR "\xEF\xA0\x9D" /*63517, f81d*/ +#define LV_SYMBOL_EXT_SACK_XMARK "\xEE\x95\xAA" /*58730, e56a*/ +#define LV_SYMBOL_EXT_SAFARI "\xEF\x89\xA7" /*62055, f267*/ +#define LV_SYMBOL_EXT_SAILBOAT "\xEE\x91\x85" /*58437, e445*/ +#define LV_SYMBOL_EXT_SALESFORCE "\xEF\xA0\xBB" /*63547, f83b*/ +#define LV_SYMBOL_EXT_SASS "\xEF\x90\x9E" /*62494, f41e*/ +#define LV_SYMBOL_EXT_SATELLITE "\xEF\x9E\xBF" /*63423, f7bf*/ +#define LV_SYMBOL_EXT_SATELLITE_DISH "\xEF\x9F\x80" /*63424, f7c0*/ +#define LV_SYMBOL_EXT_SCALE_BALANCED "\xEF\x89\x8E" /*62030, f24e*/ +#define LV_SYMBOL_EXT_SCALE_UNBALANCED "\xEF\x94\x95" /*62741, f515*/ +#define LV_SYMBOL_EXT_SCALE_UNBALANCED_FLIP "\xEF\x94\x96" /*62742, f516*/ +#define LV_SYMBOL_EXT_SCHLIX "\xEF\x8F\xAA" /*62442, f3ea*/ +#define LV_SYMBOL_EXT_SCHOOL "\xEF\x95\x89" /*62793, f549*/ +#define LV_SYMBOL_EXT_SCHOOL_CIRCLE_CHECK "\xEE\x95\xAB" /*58731, e56b*/ +#define LV_SYMBOL_EXT_SCHOOL_CIRCLE_EXCLAMATION "\xEE\x95\xAC" /*58732, e56c*/ +#define LV_SYMBOL_EXT_SCHOOL_CIRCLE_XMARK "\xEE\x95\xAD" /*58733, e56d*/ +#define LV_SYMBOL_EXT_SCHOOL_FLAG "\xEE\x95\xAE" /*58734, e56e*/ +#define LV_SYMBOL_EXT_SCHOOL_LOCK "\xEE\x95\xAF" /*58735, e56f*/ +#define LV_SYMBOL_EXT_SCISSORS "\xEF\x83\x84" /*61636, f0c4*/ +#define LV_SYMBOL_EXT_SCREENPAL "\xEE\x95\xB0" /*58736, e570*/ +#define LV_SYMBOL_EXT_SCREWDRIVER "\xEF\x95\x8A" /*62794, f54a*/ +#define LV_SYMBOL_EXT_SCREWDRIVER_WRENCH "\xEF\x9F\x99" /*63449, f7d9*/ +#define LV_SYMBOL_EXT_SCRIBD "\xEF\x8A\x8A" /*62090, f28a*/ +#define LV_SYMBOL_EXT_SCROLL "\xEF\x9C\x8E" /*63246, f70e*/ +#define LV_SYMBOL_EXT_SCROLL_TORAH "\xEF\x9A\xA0" /*63136, f6a0*/ +#define LV_SYMBOL_EXT_SD_CARD "\xEF\x9F\x82" /*63426, f7c2*/ +#define LV_SYMBOL_EXT_SEARCHENGIN "\xEF\x8F\xAB" /*62443, f3eb*/ +#define LV_SYMBOL_EXT_SECTION "\xEE\x91\x87" /*58439, e447*/ +#define LV_SYMBOL_EXT_SEEDLING "\xEF\x93\x98" /*62680, f4d8*/ +#define LV_SYMBOL_EXT_SELLCAST "\xEF\x8B\x9A" /*62170, f2da*/ +#define LV_SYMBOL_EXT_SELLSY "\xEF\x88\x93" /*61971, f213*/ +#define LV_SYMBOL_EXT_SERVER "\xEF\x88\xB3" /*62003, f233*/ +#define LV_SYMBOL_EXT_SERVICESTACK "\xEF\x8F\xAC" /*62444, f3ec*/ +#define LV_SYMBOL_EXT_SHAPES "\xEF\x98\x9F" /*63007, f61f*/ +#define LV_SYMBOL_EXT_SHARE "\xEF\x81\xA4" /*61540, f064*/ +#define LV_SYMBOL_EXT_SHARE_FROM_SQUARE "\xEF\x85\x8D" /*61773, f14d*/ +#define LV_SYMBOL_EXT_SHARE_NODES "\xEF\x87\xA0" /*61920, f1e0*/ +#define LV_SYMBOL_EXT_SHEET_PLASTIC "\xEE\x95\xB1" /*58737, e571*/ +#define LV_SYMBOL_EXT_SHEKEL_SIGN "\xEF\x88\x8B" /*61963, f20b*/ +#define LV_SYMBOL_EXT_SHIELD "\xEF\x84\xB2" /*61746, f132*/ +#define LV_SYMBOL_EXT_SHIELD_CAT "\xEE\x95\xB2" /*58738, e572*/ +#define LV_SYMBOL_EXT_SHIELD_DOG "\xEE\x95\xB3" /*58739, e573*/ +#define LV_SYMBOL_EXT_SHIELD_HALVED "\xEF\x8F\xAD" /*62445, f3ed*/ +#define LV_SYMBOL_EXT_SHIELD_HEART "\xEE\x95\xB4" /*58740, e574*/ +#define LV_SYMBOL_EXT_SHIELD_VIRUS "\xEE\x81\xAC" /*57452, e06c*/ +#define LV_SYMBOL_EXT_SHIP "\xEF\x88\x9A" /*61978, f21a*/ +#define LV_SYMBOL_EXT_SHIRT "\xEF\x95\x93" /*62803, f553*/ +#define LV_SYMBOL_EXT_SHIRTSINBULK "\xEF\x88\x94" /*61972, f214*/ +#define LV_SYMBOL_EXT_SHOE_PRINTS "\xEF\x95\x8B" /*62795, f54b*/ +#define LV_SYMBOL_EXT_SHOELACE "\xEE\x98\x8C" /*58892, e60c*/ +#define LV_SYMBOL_EXT_SHOP "\xEF\x95\x8F" /*62799, f54f*/ +#define LV_SYMBOL_EXT_SHOP_LOCK "\xEE\x92\xA5" /*58533, e4a5*/ +#define LV_SYMBOL_EXT_SHOP_SLASH "\xEE\x81\xB0" /*57456, e070*/ +#define LV_SYMBOL_EXT_SHOPIFY "\xEE\x81\x97" /*57431, e057*/ +#define LV_SYMBOL_EXT_SHOPWARE "\xEF\x96\xB5" /*62901, f5b5*/ +#define LV_SYMBOL_EXT_SHOWER "\xEF\x8B\x8C" /*62156, f2cc*/ +#define LV_SYMBOL_EXT_SHRIMP "\xEE\x91\x88" /*58440, e448*/ +#define LV_SYMBOL_EXT_SHUFFLE "\xEF\x81\xB4" /*61556, f074*/ +#define LV_SYMBOL_EXT_SHUTTLE_SPACE "\xEF\x86\x97" /*61847, f197*/ +#define LV_SYMBOL_EXT_SIGN_HANGING "\xEF\x93\x99" /*62681, f4d9*/ +#define LV_SYMBOL_EXT_SIGNAL "\xEF\x80\x92" /*61458, f012*/ +#define LV_SYMBOL_EXT_SIGNAL_MESSENGER "\xEE\x99\xA3" /*58979, e663*/ +#define LV_SYMBOL_EXT_SIGNATURE "\xEF\x96\xB7" /*62903, f5b7*/ +#define LV_SYMBOL_EXT_SIGNS_POST "\xEF\x89\xB7" /*62071, f277*/ +#define LV_SYMBOL_EXT_SIM_CARD "\xEF\x9F\x84" /*63428, f7c4*/ +#define LV_SYMBOL_EXT_SIMPLYBUILT "\xEF\x88\x95" /*61973, f215*/ +#define LV_SYMBOL_EXT_SINK "\xEE\x81\xAD" /*57453, e06d*/ +#define LV_SYMBOL_EXT_SISTRIX "\xEF\x8F\xAE" /*62446, f3ee*/ +#define LV_SYMBOL_EXT_SITEMAP "\xEF\x83\xA8" /*61672, f0e8*/ +#define LV_SYMBOL_EXT_SITH "\xEF\x94\x92" /*62738, f512*/ +#define LV_SYMBOL_EXT_SITROX "\xEE\x91\x8A" /*58442, e44a*/ +#define LV_SYMBOL_EXT_SKETCH "\xEF\x9F\x86" /*63430, f7c6*/ +#define LV_SYMBOL_EXT_SKULL "\xEF\x95\x8C" /*62796, f54c*/ +#define LV_SYMBOL_EXT_SKULL_CROSSBONES "\xEF\x9C\x94" /*63252, f714*/ +#define LV_SYMBOL_EXT_SKYATLAS "\xEF\x88\x96" /*61974, f216*/ +#define LV_SYMBOL_EXT_SKYPE "\xEF\x85\xBE" /*61822, f17e*/ +#define LV_SYMBOL_EXT_SLACK "\xEF\x86\x98" /*61848, f198*/ +#define LV_SYMBOL_EXT_SLASH "\xEF\x9C\x95" /*63253, f715*/ +#define LV_SYMBOL_EXT_SLEIGH "\xEF\x9F\x8C" /*63436, f7cc*/ +#define LV_SYMBOL_EXT_SLIDERS "\xEF\x87\x9E" /*61918, f1de*/ +#define LV_SYMBOL_EXT_SLIDESHARE "\xEF\x87\xA7" /*61927, f1e7*/ +#define LV_SYMBOL_EXT_SMOG "\xEF\x9D\x9F" /*63327, f75f*/ +#define LV_SYMBOL_EXT_SMOKING "\xEF\x92\x8D" /*62605, f48d*/ +#define LV_SYMBOL_EXT_SNAPCHAT "\xEF\x8A\xAB" /*62123, f2ab*/ +#define LV_SYMBOL_EXT_SNOWFLAKE "\xEF\x8B\x9C" /*62172, f2dc*/ +#define LV_SYMBOL_EXT_SNOWMAN "\xEF\x9F\x90" /*63440, f7d0*/ +#define LV_SYMBOL_EXT_SNOWPLOW "\xEF\x9F\x92" /*63442, f7d2*/ +#define LV_SYMBOL_EXT_SOAP "\xEE\x81\xAE" /*57454, e06e*/ +#define LV_SYMBOL_EXT_SOCKS "\xEF\x9A\x96" /*63126, f696*/ +#define LV_SYMBOL_EXT_SOLAR_PANEL "\xEF\x96\xBA" /*62906, f5ba*/ +#define LV_SYMBOL_EXT_SORT "\xEF\x83\x9C" /*61660, f0dc*/ +#define LV_SYMBOL_EXT_SORT_DOWN "\xEF\x83\x9D" /*61661, f0dd*/ +#define LV_SYMBOL_EXT_SORT_UP "\xEF\x83\x9E" /*61662, f0de*/ +#define LV_SYMBOL_EXT_SOUNDCLOUD "\xEF\x86\xBE" /*61886, f1be*/ +#define LV_SYMBOL_EXT_SOURCETREE "\xEF\x9F\x93" /*63443, f7d3*/ +#define LV_SYMBOL_EXT_SPA "\xEF\x96\xBB" /*62907, f5bb*/ +#define LV_SYMBOL_EXT_SPACE_AWESOME "\xEE\x96\xAC" /*58796, e5ac*/ +#define LV_SYMBOL_EXT_SPAGHETTI_MONSTER_FLYING "\xEF\x99\xBB" /*63099, f67b*/ +#define LV_SYMBOL_EXT_SPEAKAP "\xEF\x8F\xB3" /*62451, f3f3*/ +#define LV_SYMBOL_EXT_SPEAKER_DECK "\xEF\xA0\xBC" /*63548, f83c*/ +#define LV_SYMBOL_EXT_SPELL_CHECK "\xEF\xA2\x91" /*63633, f891*/ +#define LV_SYMBOL_EXT_SPIDER "\xEF\x9C\x97" /*63255, f717*/ +#define LV_SYMBOL_EXT_SPINNER "\xEF\x84\x90" /*61712, f110*/ +#define LV_SYMBOL_EXT_SPLOTCH "\xEF\x96\xBC" /*62908, f5bc*/ +#define LV_SYMBOL_EXT_SPOON "\xEF\x8B\xA5" /*62181, f2e5*/ +#define LV_SYMBOL_EXT_SPOTIFY "\xEF\x86\xBC" /*61884, f1bc*/ +#define LV_SYMBOL_EXT_SPRAY_CAN "\xEF\x96\xBD" /*62909, f5bd*/ +#define LV_SYMBOL_EXT_SPRAY_CAN_SPARKLES "\xEF\x97\x90" /*62928, f5d0*/ +#define LV_SYMBOL_EXT_SQUARE "\xEF\x83\x88" /*61640, f0c8*/ +#define LV_SYMBOL_EXT_SQUARE_ARROW_UP_RIGHT "\xEF\x85\x8C" /*61772, f14c*/ +#define LV_SYMBOL_EXT_SQUARE_BEHANCE "\xEF\x86\xB5" /*61877, f1b5*/ +#define LV_SYMBOL_EXT_SQUARE_CARET_DOWN "\xEF\x85\x90" /*61776, f150*/ +#define LV_SYMBOL_EXT_SQUARE_CARET_LEFT "\xEF\x86\x91" /*61841, f191*/ +#define LV_SYMBOL_EXT_SQUARE_CARET_RIGHT "\xEF\x85\x92" /*61778, f152*/ +#define LV_SYMBOL_EXT_SQUARE_CARET_UP "\xEF\x85\x91" /*61777, f151*/ +#define LV_SYMBOL_EXT_SQUARE_CHECK "\xEF\x85\x8A" /*61770, f14a*/ +#define LV_SYMBOL_EXT_SQUARE_DRIBBBLE "\xEF\x8E\x97" /*62359, f397*/ +#define LV_SYMBOL_EXT_SQUARE_ENVELOPE "\xEF\x86\x99" /*61849, f199*/ +#define LV_SYMBOL_EXT_SQUARE_FACEBOOK "\xEF\x82\x82" /*61570, f082*/ +#define LV_SYMBOL_EXT_SQUARE_FONT_AWESOME "\xEE\x96\xAD" /*58797, e5ad*/ +#define LV_SYMBOL_EXT_SQUARE_FONT_AWESOME_STROKE "\xEF\x8D\x9C" /*62300, f35c*/ +#define LV_SYMBOL_EXT_SQUARE_FULL "\xEF\x91\x9C" /*62556, f45c*/ +#define LV_SYMBOL_EXT_SQUARE_GIT "\xEF\x87\x92" /*61906, f1d2*/ +#define LV_SYMBOL_EXT_SQUARE_GITHUB "\xEF\x82\x92" /*61586, f092*/ +#define LV_SYMBOL_EXT_SQUARE_GITLAB "\xEE\x96\xAE" /*58798, e5ae*/ +#define LV_SYMBOL_EXT_SQUARE_GOOGLE_PLUS "\xEF\x83\x94" /*61652, f0d4*/ +#define LV_SYMBOL_EXT_SQUARE_H "\xEF\x83\xBD" /*61693, f0fd*/ +#define LV_SYMBOL_EXT_SQUARE_HACKER_NEWS "\xEF\x8E\xAF" /*62383, f3af*/ +#define LV_SYMBOL_EXT_SQUARE_INSTAGRAM "\xEE\x81\x95" /*57429, e055*/ +#define LV_SYMBOL_EXT_SQUARE_JS "\xEF\x8E\xB9" /*62393, f3b9*/ +#define LV_SYMBOL_EXT_SQUARE_LASTFM "\xEF\x88\x83" /*61955, f203*/ +#define LV_SYMBOL_EXT_SQUARE_LETTERBOXD "\xEE\x98\xAE" /*58926, e62e*/ +#define LV_SYMBOL_EXT_SQUARE_MINUS "\xEF\x85\x86" /*61766, f146*/ +#define LV_SYMBOL_EXT_SQUARE_NFI "\xEE\x95\xB6" /*58742, e576*/ +#define LV_SYMBOL_EXT_SQUARE_ODNOKLASSNIKI "\xEF\x89\xA4" /*62052, f264*/ +#define LV_SYMBOL_EXT_SQUARE_PARKING "\xEF\x95\x80" /*62784, f540*/ +#define LV_SYMBOL_EXT_SQUARE_PEN "\xEF\x85\x8B" /*61771, f14b*/ +#define LV_SYMBOL_EXT_SQUARE_PERSON_CONFINED "\xEE\x95\xB7" /*58743, e577*/ +#define LV_SYMBOL_EXT_SQUARE_PHONE "\xEF\x82\x98" /*61592, f098*/ +#define LV_SYMBOL_EXT_SQUARE_PHONE_FLIP "\xEF\xA1\xBB" /*63611, f87b*/ +#define LV_SYMBOL_EXT_SQUARE_PIED_PIPER "\xEE\x80\x9E" /*57374, e01e*/ +#define LV_SYMBOL_EXT_SQUARE_PINTEREST "\xEF\x83\x93" /*61651, f0d3*/ +#define LV_SYMBOL_EXT_SQUARE_PLUS "\xEF\x83\xBE" /*61694, f0fe*/ +#define LV_SYMBOL_EXT_SQUARE_POLL_HORIZONTAL "\xEF\x9A\x82" /*63106, f682*/ +#define LV_SYMBOL_EXT_SQUARE_POLL_VERTICAL "\xEF\x9A\x81" /*63105, f681*/ +#define LV_SYMBOL_EXT_SQUARE_REDDIT "\xEF\x86\xA2" /*61858, f1a2*/ +#define LV_SYMBOL_EXT_SQUARE_ROOT_VARIABLE "\xEF\x9A\x98" /*63128, f698*/ +#define LV_SYMBOL_EXT_SQUARE_RSS "\xEF\x85\x83" /*61763, f143*/ +#define LV_SYMBOL_EXT_SQUARE_SHARE_NODES "\xEF\x87\xA1" /*61921, f1e1*/ +#define LV_SYMBOL_EXT_SQUARE_SNAPCHAT "\xEF\x8A\xAD" /*62125, f2ad*/ +#define LV_SYMBOL_EXT_SQUARE_STEAM "\xEF\x86\xB7" /*61879, f1b7*/ +#define LV_SYMBOL_EXT_SQUARE_THREADS "\xEE\x98\x99" /*58905, e619*/ +#define LV_SYMBOL_EXT_SQUARE_TUMBLR "\xEF\x85\xB4" /*61812, f174*/ +#define LV_SYMBOL_EXT_SQUARE_TWITTER "\xEF\x82\x81" /*61569, f081*/ +#define LV_SYMBOL_EXT_SQUARE_UP_RIGHT "\xEF\x8D\xA0" /*62304, f360*/ +#define LV_SYMBOL_EXT_SQUARE_VIADEO "\xEF\x8A\xAA" /*62122, f2aa*/ +#define LV_SYMBOL_EXT_SQUARE_VIMEO "\xEF\x86\x94" /*61844, f194*/ +#define LV_SYMBOL_EXT_SQUARE_VIRUS "\xEE\x95\xB8" /*58744, e578*/ +#define LV_SYMBOL_EXT_SQUARE_WHATSAPP "\xEF\x90\x8C" /*62476, f40c*/ +#define LV_SYMBOL_EXT_SQUARE_X_TWITTER "\xEE\x98\x9A" /*58906, e61a*/ +#define LV_SYMBOL_EXT_SQUARE_XING "\xEF\x85\xA9" /*61801, f169*/ +#define LV_SYMBOL_EXT_SQUARE_XMARK "\xEF\x8B\x93" /*62163, f2d3*/ +#define LV_SYMBOL_EXT_SQUARE_YOUTUBE "\xEF\x90\xB1" /*62513, f431*/ +#define LV_SYMBOL_EXT_SQUARESPACE "\xEF\x96\xBE" /*62910, f5be*/ +#define LV_SYMBOL_EXT_STACK_EXCHANGE "\xEF\x86\x8D" /*61837, f18d*/ +#define LV_SYMBOL_EXT_STACK_OVERFLOW "\xEF\x85\xAC" /*61804, f16c*/ +#define LV_SYMBOL_EXT_STACKPATH "\xEF\xA1\x82" /*63554, f842*/ +#define LV_SYMBOL_EXT_STAFF_SNAKE "\xEE\x95\xB9" /*58745, e579*/ +#define LV_SYMBOL_EXT_STAIRS "\xEE\x8A\x89" /*57993, e289*/ +#define LV_SYMBOL_EXT_STAMP "\xEF\x96\xBF" /*62911, f5bf*/ +#define LV_SYMBOL_EXT_STAPLER "\xEE\x96\xAF" /*58799, e5af*/ +#define LV_SYMBOL_EXT_STAR "\xEF\x80\x85" /*61445, f005*/ +#define LV_SYMBOL_EXT_STAR_AND_CRESCENT "\xEF\x9A\x99" /*63129, f699*/ +#define LV_SYMBOL_EXT_STAR_HALF "\xEF\x82\x89" /*61577, f089*/ +#define LV_SYMBOL_EXT_STAR_HALF_STROKE "\xEF\x97\x80" /*62912, f5c0*/ +#define LV_SYMBOL_EXT_STAR_OF_DAVID "\xEF\x9A\x9A" /*63130, f69a*/ +#define LV_SYMBOL_EXT_STAR_OF_LIFE "\xEF\x98\xA1" /*63009, f621*/ +#define LV_SYMBOL_EXT_STAYLINKED "\xEF\x8F\xB5" /*62453, f3f5*/ +#define LV_SYMBOL_EXT_STEAM "\xEF\x86\xB6" /*61878, f1b6*/ +#define LV_SYMBOL_EXT_STEAM_SYMBOL "\xEF\x8F\xB6" /*62454, f3f6*/ +#define LV_SYMBOL_EXT_STERLING_SIGN "\xEF\x85\x94" /*61780, f154*/ +#define LV_SYMBOL_EXT_STETHOSCOPE "\xEF\x83\xB1" /*61681, f0f1*/ +#define LV_SYMBOL_EXT_STICKER_MULE "\xEF\x8F\xB7" /*62455, f3f7*/ +#define LV_SYMBOL_EXT_STOP "\xEF\x81\x8D" /*61517, f04d*/ +#define LV_SYMBOL_EXT_STOPWATCH "\xEF\x8B\xB2" /*62194, f2f2*/ +#define LV_SYMBOL_EXT_STOPWATCH_20 "\xEE\x81\xAF" /*57455, e06f*/ +#define LV_SYMBOL_EXT_STORE "\xEF\x95\x8E" /*62798, f54e*/ +#define LV_SYMBOL_EXT_STORE_SLASH "\xEE\x81\xB1" /*57457, e071*/ +#define LV_SYMBOL_EXT_STRAVA "\xEF\x90\xA8" /*62504, f428*/ +#define LV_SYMBOL_EXT_STREET_VIEW "\xEF\x88\x9D" /*61981, f21d*/ +#define LV_SYMBOL_EXT_STRIKETHROUGH "\xEF\x83\x8C" /*61644, f0cc*/ +#define LV_SYMBOL_EXT_STRIPE "\xEF\x90\xA9" /*62505, f429*/ +#define LV_SYMBOL_EXT_STRIPE_S "\xEF\x90\xAA" /*62506, f42a*/ +#define LV_SYMBOL_EXT_STROOPWAFEL "\xEF\x95\x91" /*62801, f551*/ +#define LV_SYMBOL_EXT_STUBBER "\xEE\x97\x87" /*58823, e5c7*/ +#define LV_SYMBOL_EXT_STUDIOVINARI "\xEF\x8F\xB8" /*62456, f3f8*/ +#define LV_SYMBOL_EXT_STUMBLEUPON "\xEF\x86\xA4" /*61860, f1a4*/ +#define LV_SYMBOL_EXT_STUMBLEUPON_CIRCLE "\xEF\x86\xA3" /*61859, f1a3*/ +#define LV_SYMBOL_EXT_SUBSCRIPT "\xEF\x84\xAC" /*61740, f12c*/ +#define LV_SYMBOL_EXT_SUITCASE "\xEF\x83\xB2" /*61682, f0f2*/ +#define LV_SYMBOL_EXT_SUITCASE_MEDICAL "\xEF\x83\xBA" /*61690, f0fa*/ +#define LV_SYMBOL_EXT_SUITCASE_ROLLING "\xEF\x97\x81" /*62913, f5c1*/ +#define LV_SYMBOL_EXT_SUN "\xEF\x86\x85" /*61829, f185*/ +#define LV_SYMBOL_EXT_SUN_PLANT_WILT "\xEE\x95\xBA" /*58746, e57a*/ +#define LV_SYMBOL_EXT_SUPERPOWERS "\xEF\x8B\x9D" /*62173, f2dd*/ +#define LV_SYMBOL_EXT_SUPERSCRIPT "\xEF\x84\xAB" /*61739, f12b*/ +#define LV_SYMBOL_EXT_SUPPLE "\xEF\x8F\xB9" /*62457, f3f9*/ +#define LV_SYMBOL_EXT_SUSE "\xEF\x9F\x96" /*63446, f7d6*/ +#define LV_SYMBOL_EXT_SWATCHBOOK "\xEF\x97\x83" /*62915, f5c3*/ +#define LV_SYMBOL_EXT_SWIFT "\xEF\xA3\xA1" /*63713, f8e1*/ +#define LV_SYMBOL_EXT_SYMFONY "\xEF\xA0\xBD" /*63549, f83d*/ +#define LV_SYMBOL_EXT_SYNAGOGUE "\xEF\x9A\x9B" /*63131, f69b*/ +#define LV_SYMBOL_EXT_SYRINGE "\xEF\x92\x8E" /*62606, f48e*/ +#define LV_SYMBOL_EXT_T "\x54" /*84, 54*/ +#define LV_SYMBOL_EXT_TABLE "\xEF\x83\x8E" /*61646, f0ce*/ +#define LV_SYMBOL_EXT_TABLE_CELLS "\xEF\x80\x8A" /*61450, f00a*/ +#define LV_SYMBOL_EXT_TABLE_CELLS_LARGE "\xEF\x80\x89" /*61449, f009*/ +#define LV_SYMBOL_EXT_TABLE_COLUMNS "\xEF\x83\x9B" /*61659, f0db*/ +#define LV_SYMBOL_EXT_TABLE_LIST "\xEF\x80\x8B" /*61451, f00b*/ +#define LV_SYMBOL_EXT_TABLE_TENNIS_PADDLE_BALL "\xEF\x91\x9D" /*62557, f45d*/ +#define LV_SYMBOL_EXT_TABLET "\xEF\x8F\xBB" /*62459, f3fb*/ +#define LV_SYMBOL_EXT_TABLET_BUTTON "\xEF\x84\x8A" /*61706, f10a*/ +#define LV_SYMBOL_EXT_TABLET_SCREEN_BUTTON "\xEF\x8F\xBA" /*62458, f3fa*/ +#define LV_SYMBOL_EXT_TABLETS "\xEF\x92\x90" /*62608, f490*/ +#define LV_SYMBOL_EXT_TACHOGRAPH_DIGITAL "\xEF\x95\xA6" /*62822, f566*/ +#define LV_SYMBOL_EXT_TAG "\xEF\x80\xAB" /*61483, f02b*/ +#define LV_SYMBOL_EXT_TAGS "\xEF\x80\xAC" /*61484, f02c*/ +#define LV_SYMBOL_EXT_TAPE "\xEF\x93\x9B" /*62683, f4db*/ +#define LV_SYMBOL_EXT_TARP "\xEE\x95\xBB" /*58747, e57b*/ +#define LV_SYMBOL_EXT_TARP_DROPLET "\xEE\x95\xBC" /*58748, e57c*/ +#define LV_SYMBOL_EXT_TAXI "\xEF\x86\xBA" /*61882, f1ba*/ +#define LV_SYMBOL_EXT_TEAMSPEAK "\xEF\x93\xB9" /*62713, f4f9*/ +#define LV_SYMBOL_EXT_TEETH "\xEF\x98\xAE" /*63022, f62e*/ +#define LV_SYMBOL_EXT_TEETH_OPEN "\xEF\x98\xAF" /*63023, f62f*/ +#define LV_SYMBOL_EXT_TELEGRAM "\xEF\x8B\x86" /*62150, f2c6*/ +#define LV_SYMBOL_EXT_TEMPERATURE_ARROW_DOWN "\xEE\x80\xBF" /*57407, e03f*/ +#define LV_SYMBOL_EXT_TEMPERATURE_ARROW_UP "\xEE\x81\x80" /*57408, e040*/ +#define LV_SYMBOL_EXT_TEMPERATURE_EMPTY "\xEF\x8B\x8B" /*62155, f2cb*/ +#define LV_SYMBOL_EXT_TEMPERATURE_FULL "\xEF\x8B\x87" /*62151, f2c7*/ +#define LV_SYMBOL_EXT_TEMPERATURE_HALF "\xEF\x8B\x89" /*62153, f2c9*/ +#define LV_SYMBOL_EXT_TEMPERATURE_HIGH "\xEF\x9D\xA9" /*63337, f769*/ +#define LV_SYMBOL_EXT_TEMPERATURE_LOW "\xEF\x9D\xAB" /*63339, f76b*/ +#define LV_SYMBOL_EXT_TEMPERATURE_QUARTER "\xEF\x8B\x8A" /*62154, f2ca*/ +#define LV_SYMBOL_EXT_TEMPERATURE_THREE_QUARTERS "\xEF\x8B\x88" /*62152, f2c8*/ +#define LV_SYMBOL_EXT_TENCENT_WEIBO "\xEF\x87\x95" /*61909, f1d5*/ +#define LV_SYMBOL_EXT_TENGE_SIGN "\xEF\x9F\x97" /*63447, f7d7*/ +#define LV_SYMBOL_EXT_TENT "\xEE\x95\xBD" /*58749, e57d*/ +#define LV_SYMBOL_EXT_TENT_ARROW_DOWN_TO_LINE "\xEE\x95\xBE" /*58750, e57e*/ +#define LV_SYMBOL_EXT_TENT_ARROW_LEFT_RIGHT "\xEE\x95\xBF" /*58751, e57f*/ +#define LV_SYMBOL_EXT_TENT_ARROW_TURN_LEFT "\xEE\x96\x80" /*58752, e580*/ +#define LV_SYMBOL_EXT_TENT_ARROWS_DOWN "\xEE\x96\x81" /*58753, e581*/ +#define LV_SYMBOL_EXT_TENTS "\xEE\x96\x82" /*58754, e582*/ +#define LV_SYMBOL_EXT_TERMINAL "\xEF\x84\xA0" /*61728, f120*/ +#define LV_SYMBOL_EXT_TEXT_HEIGHT "\xEF\x80\xB4" /*61492, f034*/ +#define LV_SYMBOL_EXT_TEXT_SLASH "\xEF\xA1\xBD" /*63613, f87d*/ +#define LV_SYMBOL_EXT_TEXT_WIDTH "\xEF\x80\xB5" /*61493, f035*/ +#define LV_SYMBOL_EXT_THE_RED_YETI "\xEF\x9A\x9D" /*63133, f69d*/ +#define LV_SYMBOL_EXT_THEMECO "\xEF\x97\x86" /*62918, f5c6*/ +#define LV_SYMBOL_EXT_THEMEISLE "\xEF\x8A\xB2" /*62130, f2b2*/ +#define LV_SYMBOL_EXT_THERMOMETER "\xEF\x92\x91" /*62609, f491*/ +#define LV_SYMBOL_EXT_THINK_PEAKS "\xEF\x9C\xB1" /*63281, f731*/ +#define LV_SYMBOL_EXT_THREADS "\xEE\x98\x98" /*58904, e618*/ +#define LV_SYMBOL_EXT_THUMBS_DOWN "\xEF\x85\xA5" /*61797, f165*/ +#define LV_SYMBOL_EXT_THUMBS_UP "\xEF\x85\xA4" /*61796, f164*/ +#define LV_SYMBOL_EXT_THUMBTACK "\xEF\x82\x8D" /*61581, f08d*/ +#define LV_SYMBOL_EXT_TICKET "\xEF\x85\x85" /*61765, f145*/ +#define LV_SYMBOL_EXT_TICKET_SIMPLE "\xEF\x8F\xBF" /*62463, f3ff*/ +#define LV_SYMBOL_EXT_TIKTOK "\xEE\x81\xBB" /*57467, e07b*/ +#define LV_SYMBOL_EXT_TIMELINE "\xEE\x8A\x9C" /*58012, e29c*/ +#define LV_SYMBOL_EXT_TOGGLE_OFF "\xEF\x88\x84" /*61956, f204*/ +#define LV_SYMBOL_EXT_TOGGLE_ON "\xEF\x88\x85" /*61957, f205*/ +#define LV_SYMBOL_EXT_TOILET "\xEF\x9F\x98" /*63448, f7d8*/ +#define LV_SYMBOL_EXT_TOILET_PAPER "\xEF\x9C\x9E" /*63262, f71e*/ +#define LV_SYMBOL_EXT_TOILET_PAPER_SLASH "\xEE\x81\xB2" /*57458, e072*/ +#define LV_SYMBOL_EXT_TOILET_PORTABLE "\xEE\x96\x83" /*58755, e583*/ +#define LV_SYMBOL_EXT_TOILETS_PORTABLE "\xEE\x96\x84" /*58756, e584*/ +#define LV_SYMBOL_EXT_TOOLBOX "\xEF\x95\x92" /*62802, f552*/ +#define LV_SYMBOL_EXT_TOOTH "\xEF\x97\x89" /*62921, f5c9*/ +#define LV_SYMBOL_EXT_TORII_GATE "\xEF\x9A\xA1" /*63137, f6a1*/ +#define LV_SYMBOL_EXT_TORNADO "\xEF\x9D\xAF" /*63343, f76f*/ +#define LV_SYMBOL_EXT_TOWER_BROADCAST "\xEF\x94\x99" /*62745, f519*/ +#define LV_SYMBOL_EXT_TOWER_CELL "\xEE\x96\x85" /*58757, e585*/ +#define LV_SYMBOL_EXT_TOWER_OBSERVATION "\xEE\x96\x86" /*58758, e586*/ +#define LV_SYMBOL_EXT_TRACTOR "\xEF\x9C\xA2" /*63266, f722*/ +#define LV_SYMBOL_EXT_TRADE_FEDERATION "\xEF\x94\x93" /*62739, f513*/ +#define LV_SYMBOL_EXT_TRADEMARK "\xEF\x89\x9C" /*62044, f25c*/ +#define LV_SYMBOL_EXT_TRAFFIC_LIGHT "\xEF\x98\xB7" /*63031, f637*/ +#define LV_SYMBOL_EXT_TRAILER "\xEE\x81\x81" /*57409, e041*/ +#define LV_SYMBOL_EXT_TRAIN "\xEF\x88\xB8" /*62008, f238*/ +#define LV_SYMBOL_EXT_TRAIN_SUBWAY "\xEF\x88\xB9" /*62009, f239*/ +#define LV_SYMBOL_EXT_TRAIN_TRAM "\xEE\x96\xB4" /*58804, e5b4*/ +#define LV_SYMBOL_EXT_TRANSGENDER "\xEF\x88\xA5" /*61989, f225*/ +#define LV_SYMBOL_EXT_TRASH "\xEF\x87\xB8" /*61944, f1f8*/ +#define LV_SYMBOL_EXT_TRASH_ARROW_UP "\xEF\xA0\xA9" /*63529, f829*/ +#define LV_SYMBOL_EXT_TRASH_CAN "\xEF\x8B\xAD" /*62189, f2ed*/ +#define LV_SYMBOL_EXT_TRASH_CAN_ARROW_UP "\xEF\xA0\xAA" /*63530, f82a*/ +#define LV_SYMBOL_EXT_TREE "\xEF\x86\xBB" /*61883, f1bb*/ +#define LV_SYMBOL_EXT_TREE_CITY "\xEE\x96\x87" /*58759, e587*/ +#define LV_SYMBOL_EXT_TRELLO "\xEF\x86\x81" /*61825, f181*/ +#define LV_SYMBOL_EXT_TRIANGLE_EXCLAMATION "\xEF\x81\xB1" /*61553, f071*/ +#define LV_SYMBOL_EXT_TROPHY "\xEF\x82\x91" /*61585, f091*/ +#define LV_SYMBOL_EXT_TROWEL "\xEE\x96\x89" /*58761, e589*/ +#define LV_SYMBOL_EXT_TROWEL_BRICKS "\xEE\x96\x8A" /*58762, e58a*/ +#define LV_SYMBOL_EXT_TRUCK "\xEF\x83\x91" /*61649, f0d1*/ +#define LV_SYMBOL_EXT_TRUCK_ARROW_RIGHT "\xEE\x96\x8B" /*58763, e58b*/ +#define LV_SYMBOL_EXT_TRUCK_DROPLET "\xEE\x96\x8C" /*58764, e58c*/ +#define LV_SYMBOL_EXT_TRUCK_FAST "\xEF\x92\x8B" /*62603, f48b*/ +#define LV_SYMBOL_EXT_TRUCK_FIELD "\xEE\x96\x8D" /*58765, e58d*/ +#define LV_SYMBOL_EXT_TRUCK_FIELD_UN "\xEE\x96\x8E" /*58766, e58e*/ +#define LV_SYMBOL_EXT_TRUCK_FRONT "\xEE\x8A\xB7" /*58039, e2b7*/ +#define LV_SYMBOL_EXT_TRUCK_MEDICAL "\xEF\x83\xB9" /*61689, f0f9*/ +#define LV_SYMBOL_EXT_TRUCK_MONSTER "\xEF\x98\xBB" /*63035, f63b*/ +#define LV_SYMBOL_EXT_TRUCK_MOVING "\xEF\x93\x9F" /*62687, f4df*/ +#define LV_SYMBOL_EXT_TRUCK_PICKUP "\xEF\x98\xBC" /*63036, f63c*/ +#define LV_SYMBOL_EXT_TRUCK_PLANE "\xEE\x96\x8F" /*58767, e58f*/ +#define LV_SYMBOL_EXT_TRUCK_RAMP_BOX "\xEF\x93\x9E" /*62686, f4de*/ +#define LV_SYMBOL_EXT_TTY "\xEF\x87\xA4" /*61924, f1e4*/ +#define LV_SYMBOL_EXT_TUMBLR "\xEF\x85\xB3" /*61811, f173*/ +#define LV_SYMBOL_EXT_TURKISH_LIRA_SIGN "\xEE\x8A\xBB" /*58043, e2bb*/ +#define LV_SYMBOL_EXT_TURN_DOWN "\xEF\x8E\xBE" /*62398, f3be*/ +#define LV_SYMBOL_EXT_TURN_UP "\xEF\x8E\xBF" /*62399, f3bf*/ +#define LV_SYMBOL_EXT_TV "\xEF\x89\xAC" /*62060, f26c*/ +#define LV_SYMBOL_EXT_TWITCH "\xEF\x87\xA8" /*61928, f1e8*/ +#define LV_SYMBOL_EXT_TWITTER "\xEF\x82\x99" /*61593, f099*/ +#define LV_SYMBOL_EXT_TYPO3 "\xEF\x90\xAB" /*62507, f42b*/ +#define LV_SYMBOL_EXT_U "\x55" /*85, 55*/ +#define LV_SYMBOL_EXT_UBER "\xEF\x90\x82" /*62466, f402*/ +#define LV_SYMBOL_EXT_UBUNTU "\xEF\x9F\x9F" /*63455, f7df*/ +#define LV_SYMBOL_EXT_UIKIT "\xEF\x90\x83" /*62467, f403*/ +#define LV_SYMBOL_EXT_UMBRACO "\xEF\xA3\xA8" /*63720, f8e8*/ +#define LV_SYMBOL_EXT_UMBRELLA "\xEF\x83\xA9" /*61673, f0e9*/ +#define LV_SYMBOL_EXT_UMBRELLA_BEACH "\xEF\x97\x8A" /*62922, f5ca*/ +#define LV_SYMBOL_EXT_UNCHARTED "\xEE\x82\x84" /*57476, e084*/ +#define LV_SYMBOL_EXT_UNDERLINE "\xEF\x83\x8D" /*61645, f0cd*/ +#define LV_SYMBOL_EXT_UNIREGISTRY "\xEF\x90\x84" /*62468, f404*/ +#define LV_SYMBOL_EXT_UNITY "\xEE\x81\x89" /*57417, e049*/ +#define LV_SYMBOL_EXT_UNIVERSAL_ACCESS "\xEF\x8A\x9A" /*62106, f29a*/ +#define LV_SYMBOL_EXT_UNLOCK "\xEF\x82\x9C" /*61596, f09c*/ +#define LV_SYMBOL_EXT_UNLOCK_KEYHOLE "\xEF\x84\xBE" /*61758, f13e*/ +#define LV_SYMBOL_EXT_UNSPLASH "\xEE\x81\xBC" /*57468, e07c*/ +#define LV_SYMBOL_EXT_UNTAPPD "\xEF\x90\x85" /*62469, f405*/ +#define LV_SYMBOL_EXT_UP_DOWN "\xEF\x8C\xB8" /*62264, f338*/ +#define LV_SYMBOL_EXT_UP_DOWN_LEFT_RIGHT "\xEF\x82\xB2" /*61618, f0b2*/ +#define LV_SYMBOL_EXT_UP_LONG "\xEF\x8C\x8C" /*62220, f30c*/ +#define LV_SYMBOL_EXT_UP_RIGHT_AND_DOWN_LEFT_FROM_CENTER "\xEF\x90\xA4" /*62500, f424*/ +#define LV_SYMBOL_EXT_UP_RIGHT_FROM_SQUARE "\xEF\x8D\x9D" /*62301, f35d*/ +#define LV_SYMBOL_EXT_UPLOAD "\xEF\x82\x93" /*61587, f093*/ +#define LV_SYMBOL_EXT_UPS "\xEF\x9F\xA0" /*63456, f7e0*/ +#define LV_SYMBOL_EXT_UPWORK "\xEE\x99\x81" /*58945, e641*/ +#define LV_SYMBOL_EXT_USB "\xEF\x8A\x87" /*62087, f287*/ +#define LV_SYMBOL_EXT_USER "\xEF\x80\x87" /*61447, f007*/ +#define LV_SYMBOL_EXT_USER_ASTRONAUT "\xEF\x93\xBB" /*62715, f4fb*/ +#define LV_SYMBOL_EXT_USER_CHECK "\xEF\x93\xBC" /*62716, f4fc*/ +#define LV_SYMBOL_EXT_USER_CLOCK "\xEF\x93\xBD" /*62717, f4fd*/ +#define LV_SYMBOL_EXT_USER_DOCTOR "\xEF\x83\xB0" /*61680, f0f0*/ +#define LV_SYMBOL_EXT_USER_GEAR "\xEF\x93\xBE" /*62718, f4fe*/ +#define LV_SYMBOL_EXT_USER_GRADUATE "\xEF\x94\x81" /*62721, f501*/ +#define LV_SYMBOL_EXT_USER_GROUP "\xEF\x94\x80" /*62720, f500*/ +#define LV_SYMBOL_EXT_USER_INJURED "\xEF\x9C\xA8" /*63272, f728*/ +#define LV_SYMBOL_EXT_USER_LARGE "\xEF\x90\x86" /*62470, f406*/ +#define LV_SYMBOL_EXT_USER_LARGE_SLASH "\xEF\x93\xBA" /*62714, f4fa*/ +#define LV_SYMBOL_EXT_USER_LOCK "\xEF\x94\x82" /*62722, f502*/ +#define LV_SYMBOL_EXT_USER_MINUS "\xEF\x94\x83" /*62723, f503*/ +#define LV_SYMBOL_EXT_USER_NINJA "\xEF\x94\x84" /*62724, f504*/ +#define LV_SYMBOL_EXT_USER_NURSE "\xEF\xA0\xAF" /*63535, f82f*/ +#define LV_SYMBOL_EXT_USER_PEN "\xEF\x93\xBF" /*62719, f4ff*/ +#define LV_SYMBOL_EXT_USER_PLUS "\xEF\x88\xB4" /*62004, f234*/ +#define LV_SYMBOL_EXT_USER_SECRET "\xEF\x88\x9B" /*61979, f21b*/ +#define LV_SYMBOL_EXT_USER_SHIELD "\xEF\x94\x85" /*62725, f505*/ +#define LV_SYMBOL_EXT_USER_SLASH "\xEF\x94\x86" /*62726, f506*/ +#define LV_SYMBOL_EXT_USER_TAG "\xEF\x94\x87" /*62727, f507*/ +#define LV_SYMBOL_EXT_USER_TIE "\xEF\x94\x88" /*62728, f508*/ +#define LV_SYMBOL_EXT_USER_XMARK "\xEF\x88\xB5" /*62005, f235*/ +#define LV_SYMBOL_EXT_USERS "\xEF\x83\x80" /*61632, f0c0*/ +#define LV_SYMBOL_EXT_USERS_BETWEEN_LINES "\xEE\x96\x91" /*58769, e591*/ +#define LV_SYMBOL_EXT_USERS_GEAR "\xEF\x94\x89" /*62729, f509*/ +#define LV_SYMBOL_EXT_USERS_LINE "\xEE\x96\x92" /*58770, e592*/ +#define LV_SYMBOL_EXT_USERS_RAYS "\xEE\x96\x93" /*58771, e593*/ +#define LV_SYMBOL_EXT_USERS_RECTANGLE "\xEE\x96\x94" /*58772, e594*/ +#define LV_SYMBOL_EXT_USERS_SLASH "\xEE\x81\xB3" /*57459, e073*/ +#define LV_SYMBOL_EXT_USERS_VIEWFINDER "\xEE\x96\x95" /*58773, e595*/ +#define LV_SYMBOL_EXT_USPS "\xEF\x9F\xA1" /*63457, f7e1*/ +#define LV_SYMBOL_EXT_USSUNNAH "\xEF\x90\x87" /*62471, f407*/ +#define LV_SYMBOL_EXT_UTENSILS "\xEF\x8B\xA7" /*62183, f2e7*/ +#define LV_SYMBOL_EXT_V "\x56" /*86, 56*/ +#define LV_SYMBOL_EXT_VAADIN "\xEF\x90\x88" /*62472, f408*/ +#define LV_SYMBOL_EXT_VAN_SHUTTLE "\xEF\x96\xB6" /*62902, f5b6*/ +#define LV_SYMBOL_EXT_VAULT "\xEE\x8B\x85" /*58053, e2c5*/ +#define LV_SYMBOL_EXT_VECTOR_SQUARE "\xEF\x97\x8B" /*62923, f5cb*/ +#define LV_SYMBOL_EXT_VENUS "\xEF\x88\xA1" /*61985, f221*/ +#define LV_SYMBOL_EXT_VENUS_DOUBLE "\xEF\x88\xA6" /*61990, f226*/ +#define LV_SYMBOL_EXT_VENUS_MARS "\xEF\x88\xA8" /*61992, f228*/ +#define LV_SYMBOL_EXT_VEST "\xEE\x82\x85" /*57477, e085*/ +#define LV_SYMBOL_EXT_VEST_PATCHES "\xEE\x82\x86" /*57478, e086*/ +#define LV_SYMBOL_EXT_VIACOIN "\xEF\x88\xB7" /*62007, f237*/ +#define LV_SYMBOL_EXT_VIADEO "\xEF\x8A\xA9" /*62121, f2a9*/ +#define LV_SYMBOL_EXT_VIAL "\xEF\x92\x92" /*62610, f492*/ +#define LV_SYMBOL_EXT_VIAL_CIRCLE_CHECK "\xEE\x96\x96" /*58774, e596*/ +#define LV_SYMBOL_EXT_VIAL_VIRUS "\xEE\x96\x97" /*58775, e597*/ +#define LV_SYMBOL_EXT_VIALS "\xEF\x92\x93" /*62611, f493*/ +#define LV_SYMBOL_EXT_VIBER "\xEF\x90\x89" /*62473, f409*/ +#define LV_SYMBOL_EXT_VIDEO "\xEF\x80\xBD" /*61501, f03d*/ +#define LV_SYMBOL_EXT_VIDEO_SLASH "\xEF\x93\xA2" /*62690, f4e2*/ +#define LV_SYMBOL_EXT_VIHARA "\xEF\x9A\xA7" /*63143, f6a7*/ +#define LV_SYMBOL_EXT_VIMEO "\xEF\x90\x8A" /*62474, f40a*/ +#define LV_SYMBOL_EXT_VIMEO_V "\xEF\x89\xBD" /*62077, f27d*/ +#define LV_SYMBOL_EXT_VINE "\xEF\x87\x8A" /*61898, f1ca*/ +#define LV_SYMBOL_EXT_VIRUS "\xEE\x81\xB4" /*57460, e074*/ +#define LV_SYMBOL_EXT_VIRUS_COVID "\xEE\x92\xA8" /*58536, e4a8*/ +#define LV_SYMBOL_EXT_VIRUS_COVID_SLASH "\xEE\x92\xA9" /*58537, e4a9*/ +#define LV_SYMBOL_EXT_VIRUS_SLASH "\xEE\x81\xB5" /*57461, e075*/ +#define LV_SYMBOL_EXT_VIRUSES "\xEE\x81\xB6" /*57462, e076*/ +#define LV_SYMBOL_EXT_VK "\xEF\x86\x89" /*61833, f189*/ +#define LV_SYMBOL_EXT_VNV "\xEF\x90\x8B" /*62475, f40b*/ +#define LV_SYMBOL_EXT_VOICEMAIL "\xEF\xA2\x97" /*63639, f897*/ +#define LV_SYMBOL_EXT_VOLCANO "\xEF\x9D\xB0" /*63344, f770*/ +#define LV_SYMBOL_EXT_VOLLEYBALL "\xEF\x91\x9F" /*62559, f45f*/ +#define LV_SYMBOL_EXT_VOLUME_HIGH "\xEF\x80\xA8" /*61480, f028*/ +#define LV_SYMBOL_EXT_VOLUME_LOW "\xEF\x80\xA7" /*61479, f027*/ +#define LV_SYMBOL_EXT_VOLUME_OFF "\xEF\x80\xA6" /*61478, f026*/ +#define LV_SYMBOL_EXT_VOLUME_XMARK "\xEF\x9A\xA9" /*63145, f6a9*/ +#define LV_SYMBOL_EXT_VR_CARDBOARD "\xEF\x9C\xA9" /*63273, f729*/ +#define LV_SYMBOL_EXT_VUEJS "\xEF\x90\x9F" /*62495, f41f*/ +#define LV_SYMBOL_EXT_W "\x57" /*87, 57*/ +#define LV_SYMBOL_EXT_WALKIE_TALKIE "\xEF\xA3\xAF" /*63727, f8ef*/ +#define LV_SYMBOL_EXT_WALLET "\xEF\x95\x95" /*62805, f555*/ +#define LV_SYMBOL_EXT_WAND_MAGIC "\xEF\x83\x90" /*61648, f0d0*/ +#define LV_SYMBOL_EXT_WAND_MAGIC_SPARKLES "\xEE\x8B\x8A" /*58058, e2ca*/ +#define LV_SYMBOL_EXT_WAND_SPARKLES "\xEF\x9C\xAB" /*63275, f72b*/ +#define LV_SYMBOL_EXT_WAREHOUSE "\xEF\x92\x94" /*62612, f494*/ +#define LV_SYMBOL_EXT_WATCHMAN_MONITORING "\xEE\x82\x87" /*57479, e087*/ +#define LV_SYMBOL_EXT_WATER "\xEF\x9D\xB3" /*63347, f773*/ +#define LV_SYMBOL_EXT_WATER_LADDER "\xEF\x97\x85" /*62917, f5c5*/ +#define LV_SYMBOL_EXT_WAVE_SQUARE "\xEF\xA0\xBE" /*63550, f83e*/ +#define LV_SYMBOL_EXT_WAZE "\xEF\xA0\xBF" /*63551, f83f*/ +#define LV_SYMBOL_EXT_WEBFLOW "\xEE\x99\x9C" /*58972, e65c*/ +#define LV_SYMBOL_EXT_WEEBLY "\xEF\x97\x8C" /*62924, f5cc*/ +#define LV_SYMBOL_EXT_WEIBO "\xEF\x86\x8A" /*61834, f18a*/ +#define LV_SYMBOL_EXT_WEIGHT_HANGING "\xEF\x97\x8D" /*62925, f5cd*/ +#define LV_SYMBOL_EXT_WEIGHT_SCALE "\xEF\x92\x96" /*62614, f496*/ +#define LV_SYMBOL_EXT_WEIXIN "\xEF\x87\x97" /*61911, f1d7*/ +#define LV_SYMBOL_EXT_WHATSAPP "\xEF\x88\xB2" /*62002, f232*/ +#define LV_SYMBOL_EXT_WHEAT_AWN "\xEE\x8B\x8D" /*58061, e2cd*/ +#define LV_SYMBOL_EXT_WHEAT_AWN_CIRCLE_EXCLAMATION "\xEE\x96\x98" /*58776, e598*/ +#define LV_SYMBOL_EXT_WHEELCHAIR "\xEF\x86\x93" /*61843, f193*/ +#define LV_SYMBOL_EXT_WHEELCHAIR_MOVE "\xEE\x8B\x8E" /*58062, e2ce*/ +#define LV_SYMBOL_EXT_WHISKEY_GLASS "\xEF\x9E\xA0" /*63392, f7a0*/ +#define LV_SYMBOL_EXT_WHMCS "\xEF\x90\x8D" /*62477, f40d*/ +#define LV_SYMBOL_EXT_WIFI "\xEF\x87\xAB" /*61931, f1eb*/ +#define LV_SYMBOL_EXT_WIKIPEDIA_W "\xEF\x89\xA6" /*62054, f266*/ +#define LV_SYMBOL_EXT_WIND "\xEF\x9C\xAE" /*63278, f72e*/ +#define LV_SYMBOL_EXT_WINDOW_MAXIMIZE "\xEF\x8B\x90" /*62160, f2d0*/ +#define LV_SYMBOL_EXT_WINDOW_MINIMIZE "\xEF\x8B\x91" /*62161, f2d1*/ +#define LV_SYMBOL_EXT_WINDOW_RESTORE "\xEF\x8B\x92" /*62162, f2d2*/ +#define LV_SYMBOL_EXT_WINDOWS "\xEF\x85\xBA" /*61818, f17a*/ +#define LV_SYMBOL_EXT_WINE_BOTTLE "\xEF\x9C\xAF" /*63279, f72f*/ +#define LV_SYMBOL_EXT_WINE_GLASS "\xEF\x93\xA3" /*62691, f4e3*/ +#define LV_SYMBOL_EXT_WINE_GLASS_EMPTY "\xEF\x97\x8E" /*62926, f5ce*/ +#define LV_SYMBOL_EXT_WIRSINDHANDWERK "\xEE\x8B\x90" /*58064, e2d0*/ +#define LV_SYMBOL_EXT_WIX "\xEF\x97\x8F" /*62927, f5cf*/ +#define LV_SYMBOL_EXT_WIZARDS_OF_THE_COAST "\xEF\x9C\xB0" /*63280, f730*/ +#define LV_SYMBOL_EXT_WODU "\xEE\x82\x88" /*57480, e088*/ +#define LV_SYMBOL_EXT_WOLF_PACK_BATTALION "\xEF\x94\x94" /*62740, f514*/ +#define LV_SYMBOL_EXT_WON_SIGN "\xEF\x85\x99" /*61785, f159*/ +#define LV_SYMBOL_EXT_WORDPRESS "\xEF\x86\x9A" /*61850, f19a*/ +#define LV_SYMBOL_EXT_WORDPRESS_SIMPLE "\xEF\x90\x91" /*62481, f411*/ +#define LV_SYMBOL_EXT_WORM "\xEE\x96\x99" /*58777, e599*/ +#define LV_SYMBOL_EXT_WPBEGINNER "\xEF\x8A\x97" /*62103, f297*/ +#define LV_SYMBOL_EXT_WPEXPLORER "\xEF\x8B\x9E" /*62174, f2de*/ +#define LV_SYMBOL_EXT_WPFORMS "\xEF\x8A\x98" /*62104, f298*/ +#define LV_SYMBOL_EXT_WPRESSR "\xEF\x8F\xA4" /*62436, f3e4*/ +#define LV_SYMBOL_EXT_WRENCH "\xEF\x82\xAD" /*61613, f0ad*/ +#define LV_SYMBOL_EXT_X "\x58" /*88, 58*/ +#define LV_SYMBOL_EXT_X_RAY "\xEF\x92\x97" /*62615, f497*/ +#define LV_SYMBOL_EXT_X_TWITTER "\xEE\x98\x9B" /*58907, e61b*/ +#define LV_SYMBOL_EXT_XBOX "\xEF\x90\x92" /*62482, f412*/ +#define LV_SYMBOL_EXT_XING "\xEF\x85\xA8" /*61800, f168*/ +#define LV_SYMBOL_EXT_XMARK "\xEF\x80\x8D" /*61453, f00d*/ +#define LV_SYMBOL_EXT_XMARKS_LINES "\xEE\x96\x9A" /*58778, e59a*/ +#define LV_SYMBOL_EXT_Y "\x59" /*89, 59*/ +#define LV_SYMBOL_EXT_Y_COMBINATOR "\xEF\x88\xBB" /*62011, f23b*/ +#define LV_SYMBOL_EXT_YAHOO "\xEF\x86\x9E" /*61854, f19e*/ +#define LV_SYMBOL_EXT_YAMMER "\xEF\xA1\x80" /*63552, f840*/ +#define LV_SYMBOL_EXT_YANDEX "\xEF\x90\x93" /*62483, f413*/ +#define LV_SYMBOL_EXT_YANDEX_INTERNATIONAL "\xEF\x90\x94" /*62484, f414*/ +#define LV_SYMBOL_EXT_YARN "\xEF\x9F\xA3" /*63459, f7e3*/ +#define LV_SYMBOL_EXT_YELP "\xEF\x87\xA9" /*61929, f1e9*/ +#define LV_SYMBOL_EXT_YEN_SIGN "\xEF\x85\x97" /*61783, f157*/ +#define LV_SYMBOL_EXT_YIN_YANG "\xEF\x9A\xAD" /*63149, f6ad*/ +#define LV_SYMBOL_EXT_YOAST "\xEF\x8A\xB1" /*62129, f2b1*/ +#define LV_SYMBOL_EXT_YOUTUBE "\xEF\x85\xA7" /*61799, f167*/ +#define LV_SYMBOL_EXT_Z "\x5A" /*90, 5a*/ +#define LV_SYMBOL_EXT_ZHIHU "\xEF\x98\xBF" /*63039, f63f*/ + +#endif /* LV_SYMBOL_EXT_DEF */ \ No newline at end of file diff --git a/x_track/src/App/UI/Shutdown/Shutdown.cpp b/x_track/src/App/UI/Shutdown/Shutdown.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e18400e93f14562b7c9ab5a9ffd702e5d4a0ec00 --- /dev/null +++ b/x_track/src/App/UI/Shutdown/Shutdown.cpp @@ -0,0 +1,131 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Shutdown.h" +#include "../Startup/Startup.h" + +using namespace Page; + +APP_DESCRIPTOR_DEF(Shutdown); + +Shutdown::Shutdown() + : _model(nullptr) + , _view(nullptr) +{ +} + +Shutdown::~Shutdown() +{ +} + +void Shutdown::onInstalled() +{ + setLoadAnimType(PAGE_ANIM::OVER_TOP); +} + +void Shutdown::onViewLoad() +{ + _model = new ShutdownModel(this); + _view = new ShutdownView(this, getRoot()); +} + +void Shutdown::onViewDidLoad() +{ +} + +void Shutdown::onViewWillAppear() +{ + _model->setInterceptPageNavi(true); + _model->feedback()->trigger(DataProc::FEEDBACK_GEN_EFFECT::NOTIFICATION_WARNING); +} + +void Shutdown::onViewDidAppear() +{ + _model->env()->set("statusbar", "disable"); + _model->env()->set("navibar", "enable"); + _model->env()->set("pagenavi", "enable"); +} + +void Shutdown::onViewWillDisappear() +{ +} + +void Shutdown::onViewDidDisappear() +{ + _model->setInterceptPageNavi(false); +} + +void Shutdown::onViewUnload() +{ + delete _model; + delete _view; +} + +void Shutdown::onViewDidUnload() +{ +} + +void Shutdown::onModelEvent(ShutdownModel::EVENT_ID id, const void* param) +{ + switch (id) { + case ShutdownModel::EVENT_ID::PAGE_NAVI: { + _model->feedback()->trigger(DataProc::FEEDBACK_GEN_EFFECT::IMPACT_LIGHT); + getManager()->pop(); + } break; + + default: + break; + } +} + +void Shutdown::onViewEvent(ShutdownView::EVENT_ID id, const void* param) +{ + switch (id) { + case ShutdownView::EVENT_ID::READY: + _model->feedback()->trigger(DataProc::FEEDBACK_GEN_EFFECT::SELECTION); + break; + case ShutdownView::EVENT_ID::SHUTDOWN: { + if(_model->checkShutdownDisable()) { + LV_LOG_WARN("Shutdown disabled by user"); + return; + } + + _model->setInterceptPageNavi(false); + + /* Let Startup page show shoutdown animation */ + Startup::Param_t startupParam; + startupParam.shutdown = true; + PARAM pageParam = PAGE_PARAM_MAKE(startupParam); + getManager()->push("Startup", &pageParam); + + _model->feedback()->trigger(DataProc::FEEDBACK_GEN_EFFECT::NOTIFICATION_ERROR); + _model->env()->set("navibar", "disable"); + _model->shutdown(); + } break; + case ShutdownView::EVENT_ID::EXIT: + getManager()->pop(); + break; + + default: + break; + } +} diff --git a/x_track/src/App/UI/Shutdown/Shutdown.h b/x_track/src/App/UI/Shutdown/Shutdown.h new file mode 100644 index 0000000000000000000000000000000000000000..db2c02ae4c4f4c09593be2f8e2bcefdac7a5be91 --- /dev/null +++ b/x_track/src/App/UI/Shutdown/Shutdown.h @@ -0,0 +1,57 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __SHUTDOWN_PRESENTER_H +#define __SHUTDOWN_PRESENTER_H + +#include "ShutdownModel.h" +#include "ShutdownView.h" + +namespace Page { + +class Shutdown : public PageBase, public ShutdownModel::EventListener, public ShutdownView::EventListener { +public: + Shutdown(); + virtual ~Shutdown(); + + virtual void onInstalled(); + virtual void onViewLoad(); + virtual void onViewDidLoad(); + virtual void onViewWillAppear(); + virtual void onViewDidAppear(); + virtual void onViewWillDisappear(); + virtual void onViewDidDisappear(); + virtual void onViewUnload(); + virtual void onViewDidUnload(); + +private: + virtual void onModelEvent(ShutdownModel::EVENT_ID id, const void* param) override; + virtual void onViewEvent(ShutdownView::EVENT_ID id, const void* param) override; + +private: + ShutdownModel* _model; + ShutdownView* _view; +}; + +} + +#endif /* __SHUTDOWN_PRESENTER_H */ diff --git a/x_track/src/App/UI/Shutdown/ShutdownModel.cpp b/x_track/src/App/UI/Shutdown/ShutdownModel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..70d4df735ed4c5fff4479116fbea6d2fdece4e7b --- /dev/null +++ b/x_track/src/App/UI/Shutdown/ShutdownModel.cpp @@ -0,0 +1,78 @@ + +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "ShutdownModel.h" + +using namespace Page; + +ShutdownModel::ShutdownModel(EventListener* listener) + : DataNode(__func__, DataProc::broker()) + , _env(this) + , _feedback(this) + , _listener(listener) + , _interceptPageNavi(false) +{ + _nodePower = subscribe("Power"); + _nodePageNavi = subscribe("PageNavi"); + + setEventFilter(DataNode::EVENT_PUBLISH); +} + +ShutdownModel::~ShutdownModel() +{ +} + +int ShutdownModel::shutdown() +{ + DataProc::Power_Info_t info; + info.delayShutdownTime = 3000; + info.cmd = DataProc::POWER_CMD::SHUTDOWN; + return notify(_nodePower, &info, sizeof(info)); +} + +void ShutdownModel::setInterceptPageNavi(bool en) +{ + _interceptPageNavi = en; +} + +bool ShutdownModel::checkShutdownDisable() +{ + auto value = _env.get("shutdown"); + if (!value) { + return false; + } + + return (strcmp(value, "disable") == 0); +} + +int ShutdownModel::onEvent(DataNode::EventParam_t* param) +{ + if (_interceptPageNavi && param->tran == _nodePageNavi) { + auto info = (const DataProc::PageNavi_Info_t*)param->data_p; + if (info->cmd == DataProc::PAGE_NAVI_CMD::PUSH) { + _listener->onModelEvent(EVENT_ID::PAGE_NAVI); + } + } + + return DataNode::RES_OK; +} diff --git a/x_track/src/App/UI/Shutdown/ShutdownModel.h b/x_track/src/App/UI/Shutdown/ShutdownModel.h new file mode 100644 index 0000000000000000000000000000000000000000..4a20a5039f6cb6a304cfa0c573c28e191d0bdcb4 --- /dev/null +++ b/x_track/src/App/UI/Shutdown/ShutdownModel.h @@ -0,0 +1,73 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __SHUTDOWN_MODEL_H +#define __SHUTDOWN_MODEL_H + +#include "Service/DataProc/DataProc.h" + +namespace Page { + +class ShutdownModel : private DataNode { +public: + enum class EVENT_ID { + PAGE_NAVI, /* param: None */ + _LAST, + }; + + class EventListener { + public: + virtual void onModelEvent(EVENT_ID id, const void* param = nullptr) = 0; + }; + +public: + ShutdownModel(EventListener* listener); + ~ShutdownModel(); + int shutdown(); + void setInterceptPageNavi(bool en); + bool checkShutdownDisable(); + + DataProc::Env_Helper* env() + { + return &_env; + } + + DataProc::FeedbackGen_Helper* feedback() + { + return &_feedback; + } + +private: + DataProc::Env_Helper _env; + DataProc::FeedbackGen_Helper _feedback; + const DataNode* _nodePower; + const DataNode* _nodePageNavi; + EventListener* _listener; + bool _interceptPageNavi; + +private: + virtual int onEvent(DataNode::EventParam_t* param); +}; + +} + +#endif /* __SHUTDOWN_MODEL_H */ diff --git a/x_track/src/App/UI/Shutdown/ShutdownView.cpp b/x_track/src/App/UI/Shutdown/ShutdownView.cpp new file mode 100644 index 0000000000000000000000000000000000000000..21a83b4320a000705d8cf634082e7afc9bc2632d --- /dev/null +++ b/x_track/src/App/UI/Shutdown/ShutdownView.cpp @@ -0,0 +1,125 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "ShutdownView.h" + +using namespace Page; + +ShutdownView::ShutdownView(EventListener* listener, lv_obj_t* root) + : _listener(listener) +{ + lv_obj_add_event( + root, + [](lv_event_t* e) { + auto self = (ShutdownView*)lv_event_get_user_data(e); + self->_listener->onViewEvent(EVENT_ID::EXIT); + }, + LV_EVENT_CLICKED, + this); + + /* cont */ + lv_obj_t* cont = lv_obj_create(root); + { + lv_obj_set_size(cont, 60, lv_pct(50)); + + lv_obj_center(cont); + lv_obj_set_style_radius(cont, LV_RADIUS_CIRCLE, 0); + lv_obj_set_style_pad_hor(cont, 0, 0); + lv_obj_set_style_pad_ver(cont, 28, 0); + lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE); + + lv_obj_t* label = lv_label_create(cont); + lv_label_set_text_static(label, LV_SYMBOL_POWER); + lv_obj_set_style_text_color(label, lv_palette_main(LV_PALETTE_RED), 0); + lv_obj_align(label, LV_ALIGN_TOP_MID, 0, -10); + } + + /* label */ + { + lv_obj_t* label = lv_label_create(root); + + lv_label_set_text_static(label, _("POWER_OFF")); + lv_obj_set_style_text_font(label, lv_theme_get_font_large(label), 0); + lv_obj_align_to(label, cont, LV_ALIGN_OUT_TOP_MID, 0, -10); + } + + /* slider */ + lv_obj_t* slider = lv_slider_create(cont); + { + lv_obj_set_style_bg_opa(slider, LV_OPA_TRANSP, 0); + lv_obj_set_style_bg_opa(slider, LV_OPA_TRANSP, LV_PART_INDICATOR); + lv_obj_set_size(slider, lv_pct(50), lv_pct(100)); + lv_obj_center(slider); + + lv_obj_set_style_anim_time(slider, 300, 0); + lv_obj_set_style_bg_img_src(slider, LV_SYMBOL_UP, LV_PART_KNOB); + lv_obj_set_style_text_color(slider, lv_color_white(), LV_PART_KNOB); + + lv_obj_add_event( + slider, + [](lv_event_t* e) { + auto self = (ShutdownView*)lv_event_get_user_data(e); + auto obj = lv_event_get_current_target_obj(e); + auto value = lv_slider_get_value(obj); + + if (value == lv_slider_get_max_value(obj)) { + self->_listener->onViewEvent(EVENT_ID::SHUTDOWN, &value); + } else { + lv_slider_set_value(obj, lv_slider_get_min_value(obj), LV_ANIM_ON); + } + }, + LV_EVENT_RELEASED, this); + + lv_obj_add_event( + slider, + [](lv_event_t* e) { + auto self = (ShutdownView*)lv_event_get_user_data(e); + auto obj = lv_event_get_current_target_obj(e); + auto value = lv_slider_get_value(obj); + + if (value == lv_slider_get_max_value(obj)) { + self->_listener->onViewEvent(EVENT_ID::READY, &value); + } + }, + LV_EVENT_VALUE_CHANGED, this); + } +} + +ShutdownView::~ShutdownView() +{ +} + +lv_uintptr_t ShutdownView::msgID(MSG_ID id) +{ + return (lv_uintptr_t)this + (lv_uintptr_t)id; +} + +void ShutdownView::publish(MSG_ID id, const void* payload) +{ + lv_msg_send(msgID(id), payload); +} + +void ShutdownView::subscribe(MSG_ID id, lv_obj_t* obj, lv_event_cb_t event_cb) +{ + lv_msg_subscribe_obj(msgID(id), obj, this); + lv_obj_add_event(obj, event_cb, LV_EVENT_MSG_RECEIVED, this); +} diff --git a/x_track/src/App/UI/Shutdown/ShutdownView.h b/x_track/src/App/UI/Shutdown/ShutdownView.h new file mode 100644 index 0000000000000000000000000000000000000000..a72f985d47939b033f2b21adb3125459f3185b83 --- /dev/null +++ b/x_track/src/App/UI/Shutdown/ShutdownView.h @@ -0,0 +1,63 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __SHUTDOWN_VIEW_H +#define __SHUTDOWN_VIEW_H + +#include "../Page.h" + +namespace Page { + +class ShutdownView { +public: + enum class MSG_ID { + _LAST, + }; + + enum class EVENT_ID { + READY, + SHUTDOWN, + EXIT, + _LAST, + }; + + class EventListener { + public: + virtual void onViewEvent(EVENT_ID id, const void* param = nullptr) = 0; + }; + +public: + ShutdownView(EventListener* listener, lv_obj_t* root); + ~ShutdownView(); + void publish(MSG_ID id, const void* payload); + +private: + EventListener* _listener; + +private: + lv_uintptr_t msgID(MSG_ID id); + void subscribe(MSG_ID id, lv_obj_t* obj, lv_event_cb_t event_cb); +}; + +} + +#endif /* __SHUTDOWN_VIEW_H */ diff --git a/x_track/src/App/UI/Startup/Startup.cpp b/x_track/src/App/UI/Startup/Startup.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fd4b7cc7ea1bf07387c6d2b77d0c5d491a300f2d --- /dev/null +++ b/x_track/src/App/UI/Startup/Startup.cpp @@ -0,0 +1,126 @@ +/* + * MIT License + * Copyright (c) 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Startup.h" +#include "UI/Dashboard/Dashboard.h" + +using namespace Page; + +APP_DESCRIPTOR_DEF(Startup); + +Startup::Startup() + : _model(nullptr) + , _view(nullptr) +{ +} + +Startup::~Startup() +{ +} + +void Startup::onInstalled() +{ + setCacheEnable(false); + setLoadAnimType(PAGE_ANIM::FADE_ON); +} + +void Startup::onViewLoad() +{ + _model = new StartupModel(this); + _view = new StartupView(this, getRoot()); +} + +void Startup::onViewDidLoad() +{ +} + +void Startup::onViewWillAppear() +{ + Param_t param = { 0 }; + PAGE_GET_PARAM(param); + + _view->publish(StartupView::MSG_ID::SHOW, (void*)(lv_uintptr_t)!param.shutdown); + + /* load ephemeris when animation is done */ + if (!param.shutdown) { + lv_timer_t* timer = lv_timer_create( + [](lv_timer_t* tmr) { + auto self = (Startup*)lv_timer_get_user_data(tmr); + self->readyToExit(); + }, + 1500, this); + lv_timer_set_repeat_count(timer, 1); + } +} + +void Startup::onViewDidAppear() +{ + _model->env()->set("navibar", "disable"); + _model->env()->set("statusbar", "disable"); + _model->env()->set("pagenavi", "disable"); +} + +void Startup::onViewWillDisappear() +{ +} + +void Startup::onViewDidDisappear() +{ +} + +void Startup::onViewUnload() +{ + delete _model; + delete _view; +} + +void Startup::onViewDidUnload() +{ +} + +void Startup::onModelEvent(StartupModel::EVENT_ID id, const void* param) +{ +} + +void Startup::onViewEvent(StartupView::EVENT_ID id, const void* param) +{ +} + +void Startup::readyToExit() +{ + /* wait fade out animation done */ + lv_timer_t* timer = lv_timer_create( + [](lv_timer_t* tmr) { + auto self = (Startup*)lv_timer_get_user_data(tmr); + self->onExitTimer(); + }, + 500, this); + lv_timer_set_repeat_count(timer, 1); +} + +void Startup::onExitTimer() +{ + Dashboard::Param_t dashBoardParam; + dashBoardParam.animEnable = true; + PARAM param = PAGE_PARAM_MAKE(dashBoardParam); + getManager()->replace("Dashboard", ¶m); +} diff --git a/x_track/src/App/UI/Startup/Startup.h b/x_track/src/App/UI/Startup/Startup.h new file mode 100644 index 0000000000000000000000000000000000000000..897c454df434ec80d2ef9a28761ac2ce1c0c4322 --- /dev/null +++ b/x_track/src/App/UI/Startup/Startup.h @@ -0,0 +1,65 @@ +/* + * MIT License + * Copyright (c) 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __STARTUP_PRESENTER_H +#define __STARTUP_PRESENTER_H + +#include "StartupModel.h" +#include "StartupView.h" + +namespace Page { + +class Startup : public PageBase, public StartupModel::EventListener, public StartupView::EventListener { + +public: + typedef struct { + bool shutdown; + } Param_t; + +public: + Startup(); + virtual ~Startup(); + + virtual void onInstalled(); + virtual void onViewLoad(); + virtual void onViewDidLoad(); + virtual void onViewWillAppear(); + virtual void onViewDidAppear(); + virtual void onViewWillDisappear(); + virtual void onViewDidDisappear(); + virtual void onViewUnload(); + virtual void onViewDidUnload(); + +private: + virtual void onModelEvent(StartupModel::EVENT_ID id, const void* param) override; + virtual void onViewEvent(StartupView::EVENT_ID id, const void* param) override; + void readyToExit(); + void onExitTimer(); + +private: + StartupModel* _model; + StartupView* _view; +}; + +} + +#endif diff --git a/x_track/src/App/UI/Startup/StartupModel.cpp b/x_track/src/App/UI/Startup/StartupModel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7a1deb5ae976f357d93f449138bc4c4790bd230a --- /dev/null +++ b/x_track/src/App/UI/Startup/StartupModel.cpp @@ -0,0 +1,36 @@ +/* + * MIT License + * Copyright (c) 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "StartupModel.h" + +using namespace Page; + +StartupModel::StartupModel(EventListener* listener) + : _node(__func__, DataProc::broker(), this) + , _env(&_node) + , _listener(listener) +{ +} + +StartupModel::~StartupModel() +{ +} diff --git a/x_track/src/App/UI/Startup/StartupModel.h b/x_track/src/App/UI/Startup/StartupModel.h new file mode 100644 index 0000000000000000000000000000000000000000..4301cdf99a8cb9b998aeb83326af890473bcfd55 --- /dev/null +++ b/x_track/src/App/UI/Startup/StartupModel.h @@ -0,0 +1,54 @@ +/* + * MIT License + * Copyright (c) 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __STARTUP_MODEL_H +#define __STARTUP_MODEL_H + +#include "Service/DataProc/DataProc.h" + +namespace Page { + +class StartupModel { +public: + enum class EVENT_ID { + _LAST, + }; + + class EventListener { + public: + virtual void onModelEvent(EVENT_ID id, const void* param = nullptr) = 0; + }; + +public: + StartupModel(EventListener* listener); + ~StartupModel(); + DataProc::Env_Helper* env() { return &_env; } + +private: + DataNode _node; + DataProc::Env_Helper _env; + EventListener* _listener; +}; + +} + +#endif diff --git a/x_track/src/App/UI/Startup/StartupView.cpp b/x_track/src/App/UI/Startup/StartupView.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a9e79ff4e37cfad7ec146dc925b14c0959a50bf5 --- /dev/null +++ b/x_track/src/App/UI/Startup/StartupView.cpp @@ -0,0 +1,115 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "StartupView.h" +#include "Utils/lv_ext/lv_anim_timeline_wrapper.h" +#include "Utils/lv_ext/lv_ext_func.h" +#include "Version.h" + +using namespace Page; + +#define COLOR_ORANGE lv_color_hex(0xff931e) + +StartupView::StartupView(EventListener* listener, lv_obj_t* root) + : _listener(listener) + , _font(36, "medium") +{ + static_assert(sizeof(StartupView) >= (size_t)MSG_ID::_LAST, "Large MSG_ID"); + + lv_obj_set_style_pad_all(root, 0, 0); + + lv_obj_t* cont = lv_obj_create(root); + lv_obj_remove_style_all(cont); + lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_style_border_color(cont, COLOR_ORANGE, 0); + lv_obj_set_style_border_side(cont, LV_BORDER_SIDE_BOTTOM, 0); + lv_obj_set_style_border_width(cont, 3, 0); + lv_obj_set_style_border_post(cont, true, 0); + lv_obj_center(cont); + + lv_obj_t* label = lv_label_create(cont); + lv_obj_set_style_text_font(label, _font, 0); + lv_label_set_text_static(label, VERSION_FIRMWARE_NAME); + + lv_obj_update_layout(label); + int32_t contWidth = lv_obj_get_width(label); + int32_t contHeight = lv_obj_get_height(label); + lv_obj_set_size(cont, contWidth, contHeight); + + _anim_timeline = lv_anim_timeline_create(); + + auto lv_obj_set_opa = [](void* obj, int32_t v) { + lv_obj_set_style_opa((lv_obj_t*)obj, v, 0); + }; + +#define ANIM_DEF(start_time, obj, attr, start, end) \ + { start_time, obj, LV_ANIM_EXEC(attr), start, end, 500, lv_anim_path_ease_out, true } + + lv_anim_timeline_wrapper_t wrapper[] = { + ANIM_DEF(0, cont, width, 0, contWidth), + ANIM_DEF(500, label, y, contHeight, lv_obj_get_y(label)), + ANIM_DEF(1500, cont, opa, LV_OPA_COVER, LV_OPA_TRANSP), + LV_ANIM_TIMELINE_WRAPPER_END + }; + + lv_anim_timeline_add_wrapper(_anim_timeline, wrapper); + + subscribe(MSG_ID::SHOW, cont, + [](lv_event_t* e) { + auto self = (StartupView*)lv_event_get_user_data(e); + auto msg = lv_event_get_msg(e); + + if (lv_msg_get_id(msg) != self->msgID(MSG_ID::SHOW)) { + return; + } + + auto show = (bool)(lv_uintptr_t)lv_msg_get_payload(msg); + + lv_anim_timeline_set_reverse(self->_anim_timeline, !show); + if (!show) { + /* set progress to max to hide the view */ + lv_anim_timeline_set_progress(self->_anim_timeline, LV_ANIM_TIMELINE_PROGRESS_MAX); + } + lv_anim_timeline_start(self->_anim_timeline); + }); +} + +StartupView::~StartupView() +{ + lv_anim_timeline_delete(_anim_timeline); +} + +lv_uintptr_t StartupView::msgID(MSG_ID id) +{ + return (lv_uintptr_t)this + (lv_uintptr_t)id; +} + +void StartupView::publish(MSG_ID id, const void* payload) +{ + lv_msg_send(msgID(id), payload); +} + +void StartupView::subscribe(MSG_ID id, lv_obj_t* obj, lv_event_cb_t event_cb) +{ + lv_msg_subscribe_obj(msgID(id), obj, this); + lv_obj_add_event(obj, event_cb, LV_EVENT_MSG_RECEIVED, this); +} diff --git a/x_track/src/App/UI/Startup/StartupView.h b/x_track/src/App/UI/Startup/StartupView.h new file mode 100644 index 0000000000000000000000000000000000000000..21c4ee59e098a7a40d5c964ceb746a57b591329a --- /dev/null +++ b/x_track/src/App/UI/Startup/StartupView.h @@ -0,0 +1,63 @@ +/* + * MIT License + * Copyright (c) 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __STARTUP_VIEW_H +#define __STARTUP_VIEW_H + +#include "../Page.h" + +namespace Page { + +class StartupView { +public: + enum class EVENT_ID { + _LAST, + }; + + enum class MSG_ID { + SHOW, /* param: bool */ + _LAST, + }; + + class EventListener { + public: + virtual void onViewEvent(EVENT_ID id, const void* param = nullptr) = 0; + }; + +public: + StartupView(EventListener* listener, lv_obj_t* root); + ~StartupView(); + void publish(MSG_ID id, const void* payload); + +private: + EventListener* _listener; + ResourcePool::Font _font; + lv_anim_timeline_t* _anim_timeline; + +private: + lv_uintptr_t msgID(MSG_ID id); + void subscribe(MSG_ID id, lv_obj_t* obj, lv_event_cb_t event_cb); +}; + +} + +#endif /* __STARTUP_VIEW_H */ diff --git a/x_track/src/App/UI/StatusBar/StatusBar.cpp b/x_track/src/App/UI/StatusBar/StatusBar.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9e3025ea83cbf72a323692994ebf138f530296ed --- /dev/null +++ b/x_track/src/App/UI/StatusBar/StatusBar.cpp @@ -0,0 +1,88 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "StatusBar.h" +#include "Frameworks/PageManager/PageManager.h" + +#define IS_STR_EQ(STR1, STR2) (strcmp(STR1, STR2) == 0) + +using namespace Page; + +StatusBar::StatusBar(lv_obj_t* parent) +{ + _model = new StatusBarModel(this); + _view = new StatusBarView(this, parent); +} + +StatusBar::~StatusBar() +{ + delete _view; + delete _model; +} + +void StatusBar::onModelEvent(StatusBarModel::EVENT_ID id, const void* param) +{ + switch (id) { + case StatusBarModel::EVENT_ID::CLOCK: { + _view->publish(StatusBarView::MSG_ID::CLOCK, param); + } break; + + case StatusBarModel::EVENT_ID::GNSS: { + _view->publish(StatusBarView::MSG_ID::SAT_NUM, param); + } break; + + case StatusBarModel::EVENT_ID::POWER: { + _view->publish(StatusBarView::MSG_ID::POWER, param); + } break; + + case StatusBarModel::EVENT_ID::ENV: { + auto envInfo = (const DataProc::Env_Info_t*)param; + if (IS_STR_EQ(envInfo->key, "statusbar-opa")) { + StatusBarView::STYLE_ID style = IS_STR_EQ(envInfo->value, "light") + ? StatusBarView::STYLE_ID::LIGHT + : StatusBarView::STYLE_ID::DARK; + _view->publish(StatusBarView::MSG_ID::STYLE_CHANGE, &style); + } else if (IS_STR_EQ(envInfo->key, "rec")) { + _view->publish(StatusBarView::MSG_ID::REC_CHANGE, envInfo->value); + } else if (IS_STR_EQ(envInfo->key, "statusbar")) { + bool show = IS_STR_EQ(envInfo->value, "enable"); + _view->publish(StatusBarView::MSG_ID::SHOW, &show); + } + } break; + + default: + break; + } +} + +void StatusBar::onViewEvent(StatusBarView::EVENT_ID id, const void* param) +{ + switch (id) { + case StatusBarView::EVENT_ID::HEIGHT_UPDATE: { + auto height = *(int*)param; + _model->env()->setInt("statusbar-padding-top", height); + } break; + + default: + break; + } +} diff --git a/x_track/src/App/UI/StatusBar/StatusBar.h b/x_track/src/App/UI/StatusBar/StatusBar.h new file mode 100644 index 0000000000000000000000000000000000000000..3d02d47031bf2e7fc48a379127ed839d4b5ace38 --- /dev/null +++ b/x_track/src/App/UI/StatusBar/StatusBar.h @@ -0,0 +1,47 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __STATUS_BAR_PRESENTER_H +#define __STATUS_BAR_PRESENTER_H + +#include "StatusBarModel.h" +#include "StatusBarView.h" + +namespace Page { + +class StatusBar : public StatusBarModel::EventListener, public StatusBarView::EventListener { +public: + StatusBar(lv_obj_t* parent); + virtual ~StatusBar(); + +private: + virtual void onModelEvent(StatusBarModel::EVENT_ID id, const void* param) override; + virtual void onViewEvent(StatusBarView::EVENT_ID id, const void* param) override; + +private: + StatusBarModel* _model; + StatusBarView* _view; +}; + +} + +#endif diff --git a/x_track/src/App/UI/StatusBar/StatusBarModel.cpp b/x_track/src/App/UI/StatusBar/StatusBarModel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..32e799172a0055fa8ef53706f91a4cf6edb4bb4e --- /dev/null +++ b/x_track/src/App/UI/StatusBar/StatusBarModel.cpp @@ -0,0 +1,70 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "StatusBarModel.h" + +using namespace Page; + +StatusBarModel::StatusBarModel(EventListener* listener) + : DataNode(__func__, DataProc::broker()) + , _env(this) + , _listener(listener) +{ + _nodeClock = subscribe("Clock"); + _nodeGNSS = subscribe("GNSS"); + _nodePower = subscribe("Power"); + _nodePageNavi = subscribe("PageNavi"); + + setEventFilter(DataNode::EVENT_PUBLISH); +} + +StatusBarModel::~StatusBarModel() +{ +} + +int StatusBarModel::onEvent(DataNode::EventParam_t* param) +{ + if (param->tran == _nodeClock) { + auto info = (const HAL::Clock_Info_t*)param->data_p; + uint16_t hhmm = (uint8_t)info->hour << 8 | (uint8_t)info->minute; + _listener->onModelEvent(EVENT_ID::CLOCK, &hhmm); + return DataNode::RES_OK; + } + + if (param->tran == _nodeGNSS) { + auto info = (const HAL::GNSS_Info_t*)param->data_p; + _listener->onModelEvent(EVENT_ID::GNSS, &info->satellites); + return DataNode::RES_OK; + } + + if (param->tran == _nodePower) { + _listener->onModelEvent(EVENT_ID::POWER, param->data_p); + return DataNode::RES_OK; + } + + if (param->tran == _env) { + _listener->onModelEvent(EVENT_ID::ENV, param->data_p); + return DataNode::RES_OK; + } + + return DataNode::RES_UNSUPPORTED_REQUEST; +} diff --git a/x_track/src/App/UI/StatusBar/StatusBarModel.h b/x_track/src/App/UI/StatusBar/StatusBarModel.h new file mode 100644 index 0000000000000000000000000000000000000000..f5607be4b7ec57b283b61582f7bd864e49865c82 --- /dev/null +++ b/x_track/src/App/UI/StatusBar/StatusBarModel.h @@ -0,0 +1,70 @@ + +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __STATUS_BAR_MODEL_H +#define __STATUS_BAR_MODEL_H + +#include "Service/DataProc/DataProc.h" + +namespace Page { + +class StatusBarModel : private DataNode { + +public: + enum class EVENT_ID { + CLOCK, /* param: uint16_t (hhmm) */ + GNSS, /* param: int16_t (satellites) */ + POWER, /* param: DataProc::Power_Info_t */ + ENV, /* param: Env_Info_t */ + SHOW, /* param: bool */ + _LAST, + }; + + class EventListener { + public: + virtual void onModelEvent(EVENT_ID id, const void* param = nullptr) = 0; + }; + +public: + StatusBarModel(EventListener* listener); + ~StatusBarModel(); + DataProc::Env_Helper* env() + { + return &_env; + } + +private: + const DataNode* _nodeClock; + const DataNode* _nodeGNSS; + const DataNode* _nodePower; + const DataNode* _nodePageNavi; + DataProc::Env_Helper _env; + EventListener* _listener; + +private: + virtual int onEvent(DataNode::EventParam_t* param); +}; + +} + +#endif diff --git a/x_track/src/App/UI/StatusBar/StatusBarView.cpp b/x_track/src/App/UI/StatusBar/StatusBarView.cpp new file mode 100644 index 0000000000000000000000000000000000000000..475b0cabad000381bc963f2a8b73a1adbf7f5026 --- /dev/null +++ b/x_track/src/App/UI/StatusBar/StatusBarView.cpp @@ -0,0 +1,330 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "StatusBarView.h" +#include "Service/DataProc/Def/DP_Power.h" +#include "Utils/lv_anim_label/lv_anim_label.h" + +using namespace Page; + +StatusBarView::StatusBarView(EventListener* listener, lv_obj_t* par) + : _listener(listener) + , _fontMedium(15, "medium") + , _fontAwesome(15, "awesome") + , _fontHandle { 0 } + , _curState { 0 } +{ + /* Ensure that MSG_ID is unique */ + static_assert(sizeof(StatusBarView) >= (size_t)MSG_ID::_LAST, "Large MSG_ID"); + + _curState.satellitesNum = -1; + _curState.battLevel = -1; + + _fontHandle = *_fontMedium; + _fontHandle.fallback = _fontAwesome; + lv_obj_set_style_text_font(par, &_fontHandle, 0); + + lv_obj_t* cont = contCreate(par); + satelliteCreate(cont); + clockCreate(cont); + recCreate(cont); + batteryCreate(cont); + + lv_obj_update_layout(cont); + int height = lv_obj_get_height(cont); + lv_obj_set_y(cont, -height); + lv_obj_set_style_translate_y(cont, height, LV_STATE_DEFAULT); + lv_obj_set_style_translate_y(cont, 0, LV_STATE_DISABLED); + _listener->onViewEvent(EVENT_ID::HEIGHT_UPDATE, &height); +} + +StatusBarView::~StatusBarView() +{ +} + +lv_obj_t* StatusBarView::contCreate(lv_obj_t* par) +{ + lv_obj_t* cont = lv_obj_create(par); + { + lv_obj_remove_flag(cont, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_size(cont, lv_pct(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(cont, 2, 0); + lv_obj_set_style_radius(cont, 0, 0); + lv_obj_set_style_border_width(cont, 0, 0); + lv_obj_add_state(cont, LV_STATE_DISABLED); + } + + /* style trans & cover */ + { + auto dark_state = LV_STATE_USER_1; + + /* style trans */ + lv_obj_set_style_bg_opa(cont, LV_OPA_TRANSP, LV_STATE_DEFAULT); + + /* style cover */ + lv_obj_set_style_bg_color(cont, lv_color_black(), dark_state); + lv_obj_set_style_bg_opa(cont, LV_OPA_60, dark_state); + lv_obj_set_style_shadow_color(cont, lv_color_black(), dark_state); + lv_obj_set_style_shadow_width(cont, 10, dark_state); + + static lv_style_transition_dsc_t tran; + static const lv_style_prop_t prop[] = { + LV_STYLE_SHADOW_WIDTH, + LV_STYLE_BG_OPA, + LV_STYLE_PROP_INV + }; + lv_style_transition_dsc_init( + &tran, + prop, + lv_anim_path_ease_out, + 200, + 0, + nullptr); + lv_obj_set_style_transition(cont, &tran, dark_state); + lv_obj_set_style_transition(cont, &tran, LV_STATE_DEFAULT); + + subscribe(MSG_ID::STYLE_CHANGE, cont, [](lv_event_t* e) { + auto obj = lv_event_get_current_target_obj(e); + auto msg = lv_event_get_msg(e); + auto self = (StatusBarView*)lv_msg_get_user_data(msg); + + if (lv_msg_get_id(msg) != self->msgID(MSG_ID::STYLE_CHANGE)) { + return; + } + + auto style = *(STYLE_ID*)lv_msg_get_payload(msg); + if (style == STYLE_ID::LIGHT) { + lv_obj_add_state(obj, LV_STATE_USER_1); + } else { + lv_obj_clear_state(obj, LV_STATE_USER_1); + } + }); + } + + /* style show & hide */ + { + static lv_style_transition_dsc_t tran; + static const lv_style_prop_t prop[] = { + LV_STYLE_TRANSLATE_Y, + LV_STYLE_PROP_INV + }; + lv_style_transition_dsc_init( + &tran, + prop, + lv_anim_path_ease_out, + 300, + 0, + nullptr); + lv_obj_set_style_transition(cont, &tran, LV_STATE_DEFAULT); + lv_obj_set_style_transition(cont, &tran, LV_STATE_DISABLED); + + subscribe(MSG_ID::SHOW, cont, [](lv_event_t* e) { + auto obj = lv_event_get_current_target_obj(e); + auto msg = lv_event_get_msg(e); + auto self = (StatusBarView*)lv_msg_get_user_data(msg); + + if (lv_msg_get_id(msg) != self->msgID(MSG_ID::SHOW)) { + return; + } + + auto show = *(bool*)lv_msg_get_payload(msg); + if (show) { + lv_obj_clear_state(obj, LV_STATE_DISABLED); + } else { + lv_obj_add_state(obj, LV_STATE_DISABLED); + } + }); + } + + return cont; +} + +void StatusBarView::satelliteCreate(lv_obj_t* par) +{ + { + lv_obj_t* label = lv_label_create(par); + lv_label_set_text_static(label, LV_SYMBOL_EXT_SATELLITE); + lv_obj_align(label, LV_ALIGN_LEFT_MID, 5, 0); + } + + { + lv_obj_t* label = lv_label_create(par); + lv_obj_align(label, LV_ALIGN_LEFT_MID, 25, 0); + lv_label_set_text(label, "00"); + + subscribe(MSG_ID::SAT_NUM, label, [](lv_event_t* e) { + auto obj = lv_event_get_current_target_obj(e); + auto msg = lv_event_get_msg(e); + auto self = (StatusBarView*)lv_msg_get_user_data(msg); + auto satnum = *(int16_t*)lv_msg_get_payload(msg); + + if (satnum == self->_curState.satellitesNum) { + return; + } + + lv_label_set_text_fmt(obj, "%02d", satnum); + self->_curState.satellitesNum = satnum; + }); + } + + int16_t satnum = 0; + publish(MSG_ID::SAT_NUM, &satnum); +} + +void StatusBarView::clockCreate(lv_obj_t* par) +{ + lv_obj_t* label = lv_label_create(par); + { + lv_obj_center(label); + lv_label_set_text(label, "00:00"); + } + + subscribe(MSG_ID::CLOCK, label, [](lv_event_t* e) { + auto obj = lv_event_get_current_target_obj(e); + auto msg = lv_event_get_msg(e); + auto hhmm = *(uint16_t*)lv_msg_get_payload(msg); + auto self = (StatusBarView*)lv_msg_get_user_data(msg); + + int hour = hhmm >> 8; + int min = hhmm & 0xFF; + int sum = hour + min; + + if (sum == self->_curState.timeCheckSum) { + return; + } + + lv_label_set_text_fmt(obj, "%02d:%02d", hour, min); + self->_curState.timeCheckSum = sum; + }); +} + +void StatusBarView::batteryCreate(lv_obj_t* par) +{ + lv_obj_t* batteryLabel = lv_label_create(par); + { + lv_label_set_text_static(batteryLabel, LV_SYMBOL_EXT_BATTERY_EMPTY); + lv_obj_align(batteryLabel, LV_ALIGN_RIGHT_MID, -30, 0); + } + + /* battery bg */ + { + lv_obj_t* bg = lv_obj_create(batteryLabel); + lv_obj_remove_style_all(bg); + lv_obj_set_size(bg, 10, 3); + lv_obj_align(bg, LV_ALIGN_LEFT_MID, 3, -1); + lv_obj_set_style_bg_color(bg, lv_color_white(), 0); + lv_obj_set_style_bg_opa(bg, LV_OPA_COVER, 0); + + subscribe(MSG_ID::POWER, bg, [](lv_event_t* e) { + auto obj = lv_event_get_current_target_obj(e); + auto msg = lv_event_get_msg(e); + auto self = (StatusBarView*)lv_msg_get_user_data(msg); + + if (lv_msg_get_id(msg) != self->msgID(MSG_ID::POWER)) { + return; + } + + auto info = (const DataProc::Power_Info_t*)lv_msg_get_payload(msg); + + if (info->level != self->_curState.battLevel) { + lv_obj_set_width(obj, lv_map(info->level, 0, 100, 0, 10)); + } + }); + } + + lv_obj_t* label = lv_label_create(par); + { + lv_obj_align(label, LV_ALIGN_RIGHT_MID, -5, 0); + lv_label_set_text(label, "-"); + + subscribe(MSG_ID::POWER, label, [](lv_event_t* e) { + auto obj = lv_event_get_current_target_obj(e); + auto msg = lv_event_get_msg(e); + auto self = (StatusBarView*)lv_msg_get_user_data(msg); + + if (lv_msg_get_id(msg) != self->msgID(MSG_ID::POWER)) { + return; + } + + auto info = (const DataProc::Power_Info_t*)lv_msg_get_payload(msg); + + if (info->level == self->_curState.battLevel) { + return; + } + + lv_label_set_text_fmt(obj, "%02d", info->level); + self->_curState.battLevel = info->level; + }); + } +} + +void StatusBarView::recCreate(lv_obj_t* par) +{ + lv_obj_t* alabel = lv_anim_label_create(par); + lv_obj_set_size(alabel, 40, 20); + lv_anim_label_set_enter_dir(alabel, LV_DIR_TOP); + lv_anim_label_set_exit_dir(alabel, LV_DIR_BOTTOM); + lv_anim_label_set_path(alabel, lv_anim_path_ease_out); + lv_anim_label_set_time(alabel, 500); + + lv_obj_align(alabel, LV_ALIGN_RIGHT_MID, -50, 0); + // lv_obj_set_style_border_color(alabel, lv_color_white(), 0); + // lv_obj_set_style_border_width(alabel, 1, 0); + + lv_anim_t a_enter; + lv_anim_init(&a_enter); + lv_anim_set_early_apply(&a_enter, true); + lv_anim_set_values(&a_enter, LV_OPA_TRANSP, LV_OPA_COVER); + lv_anim_set_exec_cb(&a_enter, [](void* var, int32_t v) { + lv_obj_set_style_opa((lv_obj_t*)var, v, 0); + }); + lv_anim_set_time(&a_enter, 300); + + lv_anim_t a_exit = a_enter; + lv_anim_set_values(&a_exit, LV_OPA_COVER, LV_OPA_TRANSP); + + lv_anim_label_set_custom_enter_anim(alabel, &a_enter); + lv_anim_label_set_custom_exit_anim(alabel, &a_exit); + + subscribe(MSG_ID::REC_CHANGE, alabel, [](lv_event_t* e) { + auto obj = lv_event_get_current_target_obj(e); + auto msg = lv_event_get_msg(e); + auto str = (const char*)lv_msg_get_payload(msg); + lv_anim_label_push_text(obj, str); + }); +} + +lv_uintptr_t StatusBarView::msgID(MSG_ID id) +{ + return (lv_uintptr_t)this + (lv_uintptr_t)id; +} + +void StatusBarView::publish(MSG_ID id, const void* payload) +{ + lv_msg_send(msgID(id), payload); +} + +void StatusBarView::subscribe(MSG_ID id, lv_obj_t* obj, lv_event_cb_t event_cb) +{ + lv_msg_subscribe_obj(msgID(id), obj, this); + lv_obj_add_event(obj, event_cb, LV_EVENT_MSG_RECEIVED, this); +} diff --git a/x_track/src/App/UI/StatusBar/StatusBarView.h b/x_track/src/App/UI/StatusBar/StatusBarView.h new file mode 100644 index 0000000000000000000000000000000000000000..5683851efae6c775a7b77c75aa047d63b37d7b95 --- /dev/null +++ b/x_track/src/App/UI/StatusBar/StatusBarView.h @@ -0,0 +1,88 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __STATUS_BAR_VIEW_H +#define __STATUS_BAR_VIEW_H + +#include "../Page.h" + +namespace Page { + +class StatusBarView { +public: + enum class STYLE_ID { + DARK, + LIGHT, + _LAST + }; + + enum class MSG_ID { + SHOW, /* param: bool */ + CLOCK, /* param: uint16_t (uint8_t)Hour << 8 | (uint8_t)Minute */ + SAT_NUM, /* param: int16_t */ + POWER, /* param: DataProc::Power_Info_t */ + STYLE_CHANGE, /* param: STYLE_ID */ + REC_CHANGE, /* param: const char* */ + _LAST, + }; + + enum class EVENT_ID { + HEIGHT_UPDATE, /* param: int */ + _LAST, + }; + + class EventListener { + public: + virtual void onViewEvent(EVENT_ID id, const void* param = nullptr) = 0; + }; + +public: + StatusBarView(EventListener* listener, lv_obj_t* par); + ~StatusBarView(); + void publish(MSG_ID id, const void* payload); + +private: + EventListener* _listener; + + ResourcePool::Font _fontMedium; + ResourcePool::Font _fontAwesome; + lv_font_t _fontHandle; + + struct { + int16_t satellitesNum; + uint8_t timeCheckSum; + uint8_t battLevel; + } _curState; + +private: + lv_uintptr_t msgID(MSG_ID id); + void subscribe(MSG_ID id, lv_obj_t* obj, lv_event_cb_t event_cb); + lv_obj_t* contCreate(lv_obj_t* par); + void satelliteCreate(lv_obj_t* par); + void clockCreate(lv_obj_t* par); + void batteryCreate(lv_obj_t* par); + void recCreate(lv_obj_t* par); +}; + +} + +#endif /* __STATUS_BAR_VIEW_H */ diff --git a/x_track/src/App/UI/SystemInfos/BindingDef.inc b/x_track/src/App/UI/SystemInfos/BindingDef.inc new file mode 100644 index 0000000000000000000000000000000000000000..c7ab169724973a0367099ce794a3899187869e51 --- /dev/null +++ b/x_track/src/App/UI/SystemInfos/BindingDef.inc @@ -0,0 +1,4 @@ +/* Binding definition */ + +BINDING_DEF(Verison, DataProc::Version_Info_t) +BINDING_DEF(Storage, DataProc::Storage_Device_Info_t) diff --git a/x_track/src/App/UI/SystemInfos/SystemInfos.cpp b/x_track/src/App/UI/SystemInfos/SystemInfos.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7533e7f722b565e2ea4a5d81490500497a8fb83b --- /dev/null +++ b/x_track/src/App/UI/SystemInfos/SystemInfos.cpp @@ -0,0 +1,112 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "SystemInfos.h" + +using namespace Page; + +APP_DESCRIPTOR_DEF(SystemInfos); + +SystemInfos::SystemInfos() + : _model(nullptr) + , _view(nullptr) +{ +} + +SystemInfos::~SystemInfos() +{ +} + +void SystemInfos::onInstalled() +{ + setBackGestureDirection(LV_DIR_RIGHT); +} + +void SystemInfos::onViewLoad() +{ + _model = new SystemInfosModel(this); + _view = new SystemInfosView(this, getRoot()); +} + +void SystemInfos::onViewDidLoad() +{ +} + +void SystemInfos::onViewWillAppear() +{ +} + +void SystemInfos::onViewDidAppear() +{ + _model->env()->set("statusbar-opa", "light"); +} + +void SystemInfos::onViewWillDisappear() +{ +} + +void SystemInfos::onViewDidDisappear() +{ +} + +void SystemInfos::onViewUnload() +{ + delete _model; + delete _view; +} + +void SystemInfos::onViewDidUnload() +{ +} + +void SystemInfos::onModelEvent(SystemInfosModel::EVENT_ID id, const void* param) +{ + switch (id) { + case SystemInfosModel::EVENT_ID::SPORT_STATUS: { + _view->publish(SystemInfosView::MSG_ID::SPORT_STATUS, param); + } break; + case SystemInfosModel::EVENT_ID::GNSS: { + _view->publish(SystemInfosView::MSG_ID::GNSS, param); + } break; + case SystemInfosModel::EVENT_ID::CLOCK: { + _view->publish(SystemInfosView::MSG_ID::CLOCK, param); + } break; + case SystemInfosModel::EVENT_ID::POWER: { + _view->publish(SystemInfosView::MSG_ID::POWER, param); + } break; + default: + break; + } +} + +void SystemInfos::onViewEvent(SystemInfosView::EVENT_ID id, const void* param) +{ + switch (id) { + case SystemInfosView::EVENT_ID::GET_BINDING: { + auto binding = (SystemInfosView::Binding_Info_t*)param; + binding->binding = _model->getBinding((SystemInfosModel::BINDING_TYPE)binding->type); + } break; + + default: + break; + } +} diff --git a/x_track/src/App/UI/SystemInfos/SystemInfos.h b/x_track/src/App/UI/SystemInfos/SystemInfos.h new file mode 100644 index 0000000000000000000000000000000000000000..84a641146d2a453a7da6b5af66fa699915f9360c --- /dev/null +++ b/x_track/src/App/UI/SystemInfos/SystemInfos.h @@ -0,0 +1,57 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __SYSTEMINFOS_PRESENTER_H +#define __SYSTEMINFOS_PRESENTER_H + +#include "SystemInfosModel.h" +#include "SystemInfosView.h" + +namespace Page { + +class SystemInfos : public PageBase, public SystemInfosModel::EventListener, public SystemInfosView::EventListener { +public: + SystemInfos(); + virtual ~SystemInfos(); + + virtual void onInstalled(); + virtual void onViewLoad(); + virtual void onViewDidLoad(); + virtual void onViewWillAppear(); + virtual void onViewDidAppear(); + virtual void onViewWillDisappear(); + virtual void onViewDidDisappear(); + virtual void onViewUnload(); + virtual void onViewDidUnload(); + +private: + SystemInfosModel* _model; + SystemInfosView* _view; + +private: + virtual void onModelEvent(SystemInfosModel::EVENT_ID id, const void* param) override; + virtual void onViewEvent(SystemInfosView::EVENT_ID id, const void* param) override; +}; + +} + +#endif /* __SYSTEMINFOS_PRESENTER_H */ diff --git a/x_track/src/App/UI/SystemInfos/SystemInfosModel.cpp b/x_track/src/App/UI/SystemInfos/SystemInfosModel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..26a9216fbc90062cd2529eb77e420bfeddd2a41c --- /dev/null +++ b/x_track/src/App/UI/SystemInfos/SystemInfosModel.cpp @@ -0,0 +1,100 @@ + +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "SystemInfosModel.h" + +using namespace Page; + +SystemInfosModel::SystemInfosModel(EventListener* listener) + : DataNode(__func__, DataProc::broker()) + , _listener(listener) + , _env(this) +{ + _nodeSportStatus = subscribe("SportStatus"); + _nodeGNSS = subscribe("GNSS"); + _nodeClock = subscribe("Clock"); + _nodePower = subscribe("Power"); + _nodeStorage = subscribe("Storage"); + _nodeVersion = subscribe("Version"); + + setEventFilter(DataNode::EVENT_PUBLISH); + + initBingdings(); +} + +SystemInfosModel::~SystemInfosModel() +{ +} + +void SystemInfosModel::initBingdings() +{ + _bindingVerison.setCallback( + /* setter */ + nullptr, + /* getter */ + [](SystemInfosModel* self) -> DataProc::Version_Info_t { + DataProc::Version_Info_t info; + self->pull(self->_nodeVersion, &info, sizeof(info)); + return info; + }, + this); + + _bindingStorage.setCallback( + /* setter */ + nullptr, + /* getter */ + [](SystemInfosModel* self) -> DataProc::Storage_Device_Info_t { + DataProc::Storage_Device_Info_t info; + self->pull(self->_nodeStorage, &info, sizeof(info)); + return info; + }, + this); +} + +void* SystemInfosModel::getBinding(BINDING_TYPE type) +{ + switch (type) { +#define BINDING_DEF(name, type) \ + case BINDING_TYPE::name: \ + return &_binding##name; +#include "BindingDef.inc" +#undef BINDING_DEF + default: + return nullptr; + } +} + +int SystemInfosModel::onEvent(DataNode::EventParam_t* param) +{ + if (param->tran == _nodeSportStatus) { + _listener->onModelEvent(EVENT_ID::SPORT_STATUS, param->data_p); + } else if (param->tran == _nodeGNSS) { + _listener->onModelEvent(EVENT_ID::GNSS, param->data_p); + } else if (param->tran == _nodeClock) { + _listener->onModelEvent(EVENT_ID::CLOCK, param->data_p); + } else if (param->tran == _nodePower) { + _listener->onModelEvent(EVENT_ID::POWER, param->data_p); + } + + return DataNode::RES_OK; +} diff --git a/x_track/src/App/UI/SystemInfos/SystemInfosModel.h b/x_track/src/App/UI/SystemInfos/SystemInfosModel.h new file mode 100644 index 0000000000000000000000000000000000000000..07f996ef9ea07cea2e430ac9b4a136047e9bc533 --- /dev/null +++ b/x_track/src/App/UI/SystemInfos/SystemInfosModel.h @@ -0,0 +1,84 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __SYSTEMINFOS_MODEL_H +#define __SYSTEMINFOS_MODEL_H + +#include "Service/DataProc/DataProc.h" +#include "Utils/Binding/Binding.h" + +namespace Page { + +class SystemInfosModel : private DataNode { +public: + enum class EVENT_ID { + SPORT_STATUS, + GNSS, + CLOCK, + POWER, + _LAST, + }; + + enum class BINDING_TYPE { +#define BINDING_DEF(name, type) name, +#include "BindingDef.inc" +#undef BINDING_DEF + }; + + typedef struct { + BINDING_TYPE type; + void* binding; + } Binding_Info_t; + + class EventListener { + public: + virtual void onModelEvent(EVENT_ID id, const void* param = nullptr) = 0; + }; + +public: + SystemInfosModel(EventListener* listener); + ~SystemInfosModel(); + void* getBinding(BINDING_TYPE type); + DataProc::Env_Helper* env() { return &_env; } + +private: + EventListener* _listener; + const DataNode* _nodeSportStatus; + const DataNode* _nodeGNSS; + const DataNode* _nodeClock; + const DataNode* _nodePower; + const DataNode* _nodeStorage; + const DataNode* _nodeVersion; + DataProc::Env_Helper _env; + +#define BINDING_DEF(name, type) Binding<type, SystemInfosModel> _binding##name; +#include "BindingDef.inc" +#undef BINDING_DEF + +private: + virtual int onEvent(DataNode::EventParam_t* param); + void initBingdings(); +}; + +} + +#endif /* __SYSTEMINFOS_MODEL_H */ diff --git a/x_track/src/App/UI/SystemInfos/SystemInfosView.cpp b/x_track/src/App/UI/SystemInfos/SystemInfosView.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2983f53206b144f7817c5d0bed376027a9077077 --- /dev/null +++ b/x_track/src/App/UI/SystemInfos/SystemInfosView.cpp @@ -0,0 +1,415 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "SystemInfosView.h" + +using namespace Page; + +#define ITEM_HEIGHT_MIN 100 + +SystemInfosView::SystemInfosView(EventListener* listener, lv_obj_t* root) + : _listener(listener) + , _fontAwesome(40, "awesome") +#define BINDING_DEF(name, type) , _binding##name((Binding<type, SystemInfosModel>*)getBinding(BINDING_TYPE::name)) +#include "BindingDef.inc" +#undef BINDING_DEF +{ + /* Ensure that MSG_ID is unique */ + static_assert(sizeof(SystemInfosView) >= (size_t)MSG_ID::_LAST, "Large MSG_ID"); + + rootInit(root); + + styleInit(); + + itemGroupCreate(root); + + onRootScroll(root); +} + +SystemInfosView::~SystemInfosView() +{ +} + +lv_uintptr_t SystemInfosView::msgID(MSG_ID id) +{ + return (lv_uintptr_t)this + (lv_uintptr_t)id; +} + +void SystemInfosView::publish(MSG_ID id, const void* payload) +{ + lv_msg_send(msgID(id), payload); +} + +void SystemInfosView::subscribe(MSG_ID id, lv_obj_t* obj, lv_event_cb_t event_cb) +{ + lv_msg_subscribe_obj(msgID(id), obj, this); + lv_obj_add_event(obj, event_cb, LV_EVENT_MSG_RECEIVED, this); +} + +void* SystemInfosView::getBinding(BINDING_TYPE type) +{ + Binding_Info_t info; + info.type = type; + info.binding = nullptr; + _listener->onViewEvent(EVENT_ID::GET_BINDING, &info); + return info.binding; +} + +void SystemInfosView::rootInit(lv_obj_t* root) +{ + lv_obj_update_layout(root); + lv_obj_set_style_pad_ver(root, ((lv_obj_get_height(root) - ITEM_HEIGHT_MIN) / 2), 0); + + lv_obj_set_flex_flow(root, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align( + root, + LV_FLEX_ALIGN_START, + LV_FLEX_ALIGN_START, + LV_FLEX_ALIGN_CENTER); + + lv_obj_add_event_cb( + root, + [](lv_event_t* e) { + onRootScroll(lv_event_get_current_target_obj(e)); + }, + LV_EVENT_SCROLL, + this); + + lv_obj_add_flag(root, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_scroll_snap_y(root, LV_SCROLL_SNAP_CENTER); + lv_obj_set_scrollbar_mode(root, LV_SCROLLBAR_MODE_OFF); +} + +void SystemInfosView::onRootScroll(lv_obj_t* obj) +{ + auto child_cnt = lv_obj_get_child_count(obj); + auto obj_height = lv_obj_get_height(obj); + auto obj_pad_top = lv_obj_get_style_pad_top(obj, 0); + auto scroll_y = lv_obj_get_scroll_y(obj); + + for (uint32_t i = 0; i < child_cnt; i++) { + auto cont = lv_obj_get_child(obj, i); + + /* skip 2 label obj */ + auto icon = lv_obj_get_child(cont, 2); + + auto cont_height = lv_obj_get_height(cont); + auto cont_y = lv_obj_get_y(cont); + auto offset_y = (obj_height / 2.0f + scroll_y) - (obj_pad_top + cont_y + cont_height / 2.0f); + + lv_anim_t a; + lv_anim_init(&a); + a.duration = cont_height; + a.act_time = LV_ABS(offset_y); + a.start_value = 70; + a.end_value = 220; + auto width = lv_anim_path_ease_out(&a); + + a.duration = cont_height / 2; + a.start_value = LV_OPA_COVER; + a.end_value = LV_OPA_TRANSP; + auto opa = lv_anim_path_ease_out(&a); + + // LV_LOG_USER("index: %d, icon: %p, offset_y: %d, width: %d", i, icon, (int)offset_y, width); + lv_obj_set_width(icon, width); + lv_obj_set_style_border_opa(icon, opa, 0); + } +} + +void SystemInfosView::styleInit() +{ + lv_style_set_pad_all(&_style.icon, 0); + lv_style_set_border_width(&_style.icon, 0); + lv_style_set_radius(&_style.icon, 0); + lv_style_set_width(&_style.icon, 220); + lv_style_set_border_side(&_style.icon, LV_BORDER_SIDE_RIGHT); + lv_style_set_border_width(&_style.icon, 2); + lv_style_set_border_color(&_style.icon, lv_color_hex(0xff931e)); + lv_style_set_text_font(&_style.icon, lv_theme_get_font_normal(nullptr)); + lv_style_set_text_color(&_style.icon, lv_color_white()); + + lv_style_set_text_font(&_style.info, lv_theme_get_font_small(nullptr)); +} + +void SystemInfosView::itemGroupCreate(lv_obj_t* par) +{ + char infoBuf[128]; + + /* Sport */ + { + lv_snprintf(infoBuf, sizeof(infoBuf), + "%s\n%s\n%s", + _("TOTAL_TRIP"), _("TOTAL_TIME"), _("MAX_SPEED")); + lv_obj_t* label = itemCreate( + par, + _("SPORT"), + LV_SYMBOL_EXT_BICYCLE, + infoBuf); + + subscribe( + MSG_ID::SPORT_STATUS, + label, + [](lv_event_t* e) { + auto obj = lv_event_get_current_target_obj(e); + auto msg = lv_event_get_msg(e); + auto info = (const DataProc::SportStatus_Info_t*)lv_msg_get_payload(msg); + lv_label_set_text_fmt( + obj, + "%0.2fkm\n" + "%s\n" + "%0.1fkm/h", + info->totalDistance / 1000.0f, + makeTimeString(info->totalTime), + info->speedMaxKph); + }); + } + + { + lv_snprintf(infoBuf, sizeof(infoBuf), + "%s\n%s\n%s\n%s\n\n%s\n%s", + _("LATITUDE"), + _("LONGITUDE"), + _("ALTITUDE"), + _("UTC_TIME"), + _("COURSE"), + _("SPEED")); + lv_obj_t* label = itemCreate( + par, + _("GNSS"), + LV_SYMBOL_EXT_SATELLITE, + infoBuf); + + subscribe( + MSG_ID::GNSS, + label, + [](lv_event_t* e) { + auto obj = lv_event_get_current_target_obj(e); + auto msg = lv_event_get_msg(e); + auto info = (const HAL::GNSS_Info_t*)lv_msg_get_payload(msg); + lv_label_set_text_fmt( + obj, + "%0.6f\n" + "%0.6f\n" + "%0.2fm\n" + "%s\n" + "%0.1f deg\n" + "%0.1fkm/h", + info->latitude, + info->longitude, + info->altitude, + makeTimeString(&info->clock), + info->course, + info->speed); + }); + } + + { + lv_snprintf(infoBuf, sizeof(infoBuf), + "%s\n%s", + _("DATE"), + _("TIME")); + lv_obj_t* label = itemCreate( + par, + _("TIME"), + LV_SYMBOL_EXT_CLOCK, + infoBuf); + + subscribe( + MSG_ID::CLOCK, + label, + [](lv_event_t* e) { + auto obj = lv_event_get_current_target_obj(e); + auto msg = lv_event_get_msg(e); + auto info = (const HAL::Clock_Info_t*)lv_msg_get_payload(msg); + lv_label_set_text_fmt( + obj, + "%s", + makeTimeString(info)); + }); + } + + { + lv_snprintf(infoBuf, sizeof(infoBuf), + "%s\n%s\n%s", + _("USAGE"), + _("VOLTAGE"), + _("STATUS")); + lv_obj_t* label = itemCreate( + par, + _("BATTERY"), + LV_SYMBOL_EXT_BATTERY_THREE_QUARTERS, + infoBuf); + + subscribe( + MSG_ID::POWER, + label, + [](lv_event_t* e) { + auto obj = lv_event_get_current_target_obj(e); + auto msg = lv_event_get_msg(e); + auto info = (const DataProc::Power_Info_t*)lv_msg_get_payload(msg); + lv_label_set_text_fmt( + obj, + "%d%%\n" + "%0.2fV\n" + "%s", + info->level, + info->voltage / 1000.0f, + info->isCharging ? _("CHARGE") : _("DISCHARGE")); + }); + } + + { + lv_snprintf(infoBuf, sizeof(infoBuf), + "%s\n%s\n%s\n%s\n%s", + _("NAME"), + _("AUTHOR"), + _("GRAPHICS"), + _("COMPILER"), + _("BUILD_DATE")); + lv_obj_t* label = itemCreate( + par, + _("ABOUT"), + LV_SYMBOL_EXT_ADDRESS_CARD, + infoBuf); + + auto info = _bindingVerison->get(); + + lv_label_set_text_fmt( + label, + "%s\n" + "%s\n" + "%s\n" + "%s\n" + "%s", + info.name, + info.author, + info.graphics, + info.compiler, + info.buildDate); + } +} + +lv_obj_t* SystemInfosView::itemCreate( + lv_obj_t* par, + const char* name, + const char* symbol, + const char* infos) +{ + lv_obj_t* cont = lv_obj_create(par); + lv_obj_enable_style_refresh(false); + lv_obj_remove_style_all(cont); + lv_obj_set_width(cont, 220); + lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE); + + /* icon */ + lv_obj_t* icon = lv_obj_create(cont); + lv_obj_enable_style_refresh(false); + lv_obj_clear_flag(icon, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_add_flag(icon, LV_OBJ_FLAG_SCROLL_ON_FOCUS); + + lv_obj_add_style(icon, &_style.icon, 0); + lv_obj_set_style_align(icon, LV_ALIGN_LEFT_MID, 0); + + lv_obj_set_flex_flow(icon, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align( + icon, + LV_FLEX_ALIGN_SPACE_AROUND, + LV_FLEX_ALIGN_CENTER, + LV_FLEX_ALIGN_CENTER); + + { + lv_obj_t* label = lv_label_create(icon); + lv_obj_enable_style_refresh(false); + lv_obj_set_style_text_font(label, _fontAwesome, 0); + lv_label_set_text_static(label, symbol); + } + + { + lv_obj_t* label = lv_label_create(icon); + lv_obj_enable_style_refresh(false); + lv_label_set_text(label, name); + } + + /* infos */ + lv_obj_t* labelInfo = lv_label_create(cont); + { + lv_obj_enable_style_refresh(false); + lv_label_set_text(labelInfo, infos); + lv_obj_add_style(labelInfo, &_style.info, 0); + lv_obj_align(labelInfo, LV_ALIGN_LEFT_MID, 75, 0); + } + + /* datas */ + lv_obj_t* labelData = lv_label_create(cont); + { + lv_obj_enable_style_refresh(false); + lv_label_set_text(labelData, "-"); + lv_obj_add_style(labelData, &_style.info, 0); + lv_obj_align(labelData, LV_ALIGN_CENTER, 60, 0); + } + + lv_obj_move_foreground(icon); + lv_obj_enable_style_refresh(true); + + /* get real max height */ + lv_obj_update_layout(labelInfo); + lv_coord_t height = lv_obj_get_height(labelInfo); + height = LV_MAX(height, ITEM_HEIGHT_MIN); + lv_obj_set_height(cont, height); + lv_obj_set_height(icon, height); + + return labelData; +} + +const char* SystemInfosView::makeTimeString(uint64_t ms) +{ + static char buf[16]; + uint64_t ss = ms / 1000; + uint64_t mm = ss / 60; + uint32_t hh = (uint32_t)(mm / 60); + + lv_snprintf( + buf, sizeof(buf), + "%d:%02d:%02d", + (int)hh, + (int)(mm % 60), + (int)(ss % 60)); + + return buf; +} + +const char* SystemInfosView::makeTimeString(const HAL::Clock_Info_t* info) +{ + static char buf[32]; + lv_snprintf( + buf, + sizeof(buf), + "%d-%d-%d\n%02d:%02d:%02d", + info->year, + info->month, + info->day, + info->hour, + info->minute, + info->second); + + return buf; +} diff --git a/x_track/src/App/UI/SystemInfos/SystemInfosView.h b/x_track/src/App/UI/SystemInfos/SystemInfosView.h new file mode 100644 index 0000000000000000000000000000000000000000..9302a8b5ce4e0e078bfefb7488911d4938dde20f --- /dev/null +++ b/x_track/src/App/UI/SystemInfos/SystemInfosView.h @@ -0,0 +1,115 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __SYSTEMINFOS_VIEW_H +#define __SYSTEMINFOS_VIEW_H + +#include "../Page.h" +#include "Service/DataProc/DataProc.h" +#include "Service/HAL/HAL.h" +#include "Utils/Binding/Binding.h" + +namespace Page { + +class SystemInfosModel; + +class SystemInfosView { +public: + enum class EVENT_ID { + GET_BINDING, /* Param: Binding_Info_t */ + _LAST, + }; + + enum class MSG_ID { + SPORT_STATUS, /* param: SportStatus_Info_t */ + GNSS, /* param: GNSS_Info_t */ + CLOCK, /* param: Clock_Info_t */ + POWER, /* param: Power_Info_t */ + _LAST, + }; + + enum class BINDING_TYPE { +#define BINDING_DEF(name, type) name, +#include "BindingDef.inc" +#undef BINDING_DEF + }; + + typedef struct { + BINDING_TYPE type; + void* binding; + } Binding_Info_t; + + class EventListener { + public: + virtual void onViewEvent(EVENT_ID id, const void* param = nullptr) = 0; + }; + +public: + SystemInfosView(EventListener* listener, lv_obj_t* root); + ~SystemInfosView(); + void publish(MSG_ID id, const void* payload = nullptr); + +private: + EventListener* _listener; + + struct STYLE { + STYLE() + { + lv_style_init(&icon); + lv_style_init(&info); + } + ~STYLE() + { + lv_style_reset(&icon); + lv_style_reset(&info); + } + lv_style_t icon; + lv_style_t info; + } _style; + + ResourcePool::Font _fontAwesome; + +#define BINDING_DEF(name, type) Binding<type, SystemInfosModel>* _binding##name; +#include "BindingDef.inc" +#undef BINDING_DEF + +private: + lv_uintptr_t msgID(MSG_ID id); + void subscribe(MSG_ID id, lv_obj_t* obj, lv_event_cb_t event_cb); + void* getBinding(BINDING_TYPE type); + void rootInit(lv_obj_t* root); + static void onRootScroll(lv_obj_t* obj); + void styleInit(); + void itemGroupCreate(lv_obj_t* par); + lv_obj_t* itemCreate( + lv_obj_t* par, + const char* name, + const char* symbol, + const char* infos); + + static const char* makeTimeString(uint64_t ms); + static const char* makeTimeString(const HAL::Clock_Info_t* info); +}; + +} + +#endif /* __SYSTEMINFOS_VIEW_H */ diff --git a/x_track/src/App/UI/_Template/Template.cpp b/x_track/src/App/UI/_Template/Template.cpp new file mode 100644 index 0000000000000000000000000000000000000000..41beabc83b4129f342e7f4cbbf72eb3b4616678d --- /dev/null +++ b/x_track/src/App/UI/_Template/Template.cpp @@ -0,0 +1,93 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Template.h" + +using namespace Page; + +APP_DESCRIPTOR_DEF(Template); + +Template::Template() + : _model(nullptr) + , _view(nullptr) +{ +} + +Template::~Template() +{ +} + +void Template::onInstalled() +{ +} + +void Template::onViewLoad() +{ + _model = new TemplateModel(this); + _view = new TemplateView(this, getRoot()); +} + +void Template::onViewDidLoad() +{ +} + +void Template::onViewWillAppear() +{ +} + +void Template::onViewDidAppear() +{ +} + +void Template::onViewWillDisappear() +{ +} + +void Template::onViewDidDisappear() +{ +} + +void Template::onViewUnload() +{ + delete _model; + delete _view; +} + +void Template::onViewDidUnload() +{ +} + +void Template::onModelEvent(TemplateModel::EVENT_ID id, const void* param) +{ + switch (id) { + default: + break; + } +} + +void Template::onViewEvent(TemplateView::EVENT_ID id, const void* param) +{ + switch (id) { + default: + break; + } +} diff --git a/x_track/src/App/UI/_Template/Template.h b/x_track/src/App/UI/_Template/Template.h new file mode 100644 index 0000000000000000000000000000000000000000..8fe36729b58e35a41aa01cb9f72823b827d30aaf --- /dev/null +++ b/x_track/src/App/UI/_Template/Template.h @@ -0,0 +1,57 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __TEMPLATE_PRESENTER_H +#define __TEMPLATE_PRESENTER_H + +#include "TemplateModel.h" +#include "TemplateView.h" + +namespace Page { + +class Template : public PageBase, public TemplateModel::EventListener, public TemplateView::EventListener { +public: + Template(); + virtual ~Template(); + + virtual void onInstalled(); + virtual void onViewLoad(); + virtual void onViewDidLoad(); + virtual void onViewWillAppear(); + virtual void onViewDidAppear(); + virtual void onViewWillDisappear(); + virtual void onViewDidDisappear(); + virtual void onViewUnload(); + virtual void onViewDidUnload(); + +private: + TemplateModel* _model; + TemplateView* _view; + +private: + virtual void onModelEvent(TemplateModel::EVENT_ID id, const void* param) override; + virtual void onViewEvent(TemplateView::EVENT_ID id, const void* param) override; +}; + +} + +#endif /* __TEMPLATE_PRESENTER_H */ diff --git a/x_track/src/App/UI/_Template/TemplateModel.cpp b/x_track/src/App/UI/_Template/TemplateModel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2af54ed96a52f021cb49bafc8ae3d4c3ece227b9 --- /dev/null +++ b/x_track/src/App/UI/_Template/TemplateModel.cpp @@ -0,0 +1,42 @@ + +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "TemplateModel.h" + +using namespace Page; + +TemplateModel::TemplateModel(EventListener* listener) + : DataNode(__func__, DataProc::broker()) + , _listener(listener) +{ + setEventFilter(DataNode::EVENT_ALL); +} + +TemplateModel::~TemplateModel() +{ +} + +int TemplateModel::onEvent(DataNode::EventParam_t* param) +{ + return DataNode::RES_OK; +} diff --git a/x_track/src/App/UI/_Template/TemplateModel.h b/x_track/src/App/UI/_Template/TemplateModel.h new file mode 100644 index 0000000000000000000000000000000000000000..c9da9c0d0a254c59c291497d78aabf2eb0721f59 --- /dev/null +++ b/x_track/src/App/UI/_Template/TemplateModel.h @@ -0,0 +1,54 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __TEMPLATE_MODEL_H +#define __TEMPLATE_MODEL_H + +#include "Service/DataProc/DataProc.h" + +namespace Page { + +class TemplateModel : private DataNode { +public: + enum class EVENT_ID { + _LAST, + }; + + class EventListener { + public: + virtual void onModelEvent(EVENT_ID id, const void* param = nullptr) = 0; + }; + +public: + TemplateModel(EventListener* listener); + ~TemplateModel(); + +private: + EventListener* _listener; + +private: + virtual int onEvent(DataNode::EventParam_t* param); +}; + +} + +#endif /* __TEMPLATE_MODEL_H */ diff --git a/x_track/src/App/UI/_Template/TemplateView.cpp b/x_track/src/App/UI/_Template/TemplateView.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e7075fb935f580f649a67b68370a863c5909ffcd --- /dev/null +++ b/x_track/src/App/UI/_Template/TemplateView.cpp @@ -0,0 +1,52 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "TemplateView.h" + +using namespace Page; + +TemplateView::TemplateView(EventListener* listener, lv_obj_t* root) + : _listener(listener) +{ + /* Ensure that MSG_ID is unique */ + static_assert(sizeof(TemplateView) >= (size_t)MSG_ID::_LAST, "Large MSG_ID"); +} + +TemplateView::~TemplateView() +{ +} + +lv_uintptr_t TemplateView::msgID(MSG_ID id) +{ + return (lv_uintptr_t)this + (lv_uintptr_t)id; +} + +void TemplateView::publish(MSG_ID id, const void* payload) +{ + lv_msg_send(msgID(id), payload); +} + +void TemplateView::subscribe(MSG_ID id, lv_obj_t* obj, lv_event_cb_t event_cb) +{ + lv_msg_subscribe_obj(msgID(id), obj, this); + lv_obj_add_event(obj, event_cb, LV_EVENT_MSG_RECEIVED, this); +} diff --git a/x_track/src/App/UI/_Template/TemplateView.h b/x_track/src/App/UI/_Template/TemplateView.h new file mode 100644 index 0000000000000000000000000000000000000000..16e5a59dd478b6c0fd48a85c2ab0a37d5d48fcd9 --- /dev/null +++ b/x_track/src/App/UI/_Template/TemplateView.h @@ -0,0 +1,60 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __TEMPLATE_VIEW_H +#define __TEMPLATE_VIEW_H + +#include "../Page.h" + +namespace Page { + +class TemplateView { +public: + enum class EVENT_ID { + _LAST, + }; + + enum class MSG_ID { + _LAST, + }; + + class EventListener { + public: + virtual void onViewEvent(EVENT_ID id, const void* param = nullptr) = 0; + }; + +public: + TemplateView(EventListener* listener, lv_obj_t* root); + ~TemplateView(); + void publish(MSG_ID id, const void* payload = nullptr); + +private: + EventListener* _listener; + +private: + lv_uintptr_t msgID(MSG_ID id); + void subscribe(MSG_ID id, lv_obj_t* obj, lv_event_cb_t event_cb); +}; + +} + +#endif /* __TEMPLATE_VIEW_H */ diff --git a/x_track/src/App/UI/app_gen.py b/x_track/src/App/UI/app_gen.py new file mode 100644 index 0000000000000000000000000000000000000000..21b218c8c86e493465a51277500c6428318c7714 --- /dev/null +++ b/x_track/src/App/UI/app_gen.py @@ -0,0 +1,67 @@ +""" +MIT License + +Copyright (c) 2024 _VIFEXTech, ChatGPT-3.5 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import os +import shutil +import sys + +def copy_template_to_new_app(template_dir, new_app_name): + # New app directory name + new_app_dir = new_app_name + + # Check if directory already exists + if os.path.exists(new_app_dir): + print(f"Directory '{new_app_dir}' already exists. Aborting.") + sys.exit(1) + + # Copy template directory to new app directory + shutil.copytree(template_dir, new_app_dir) + + # Generate new file names + new_file_names = [file.replace('Template', new_app_name) for file in os.listdir(new_app_dir)] + + # Modify file names + for old_file, new_file in zip(os.listdir(new_app_dir), new_file_names): + os.rename(os.path.join(new_app_dir, old_file), os.path.join(new_app_dir, new_file)) + + # Modify template name in file contents + for file_name in new_file_names: + file_path = os.path.join(new_app_dir, file_name) + with open(file_path, 'r') as file: + file_data = file.read() + file_data = file_data.replace('Template', new_app_name) + file_data = file_data.replace('__TEMPLATE', '__' + new_app_name.upper()) + with open(file_path, 'w') as file: + file.write(file_data) + +if __name__ == "__main__": + if len(sys.argv) != 2: + print(f"Usage: python {sys.argv[0]} <NewAppName>") + sys.exit(1) + + new_app_name = sys.argv[1] + template_dir = "_Template" + + copy_template_to_new_app(template_dir, new_app_name) + print(f"Successfully created new app '{new_app_name}' from template.") diff --git a/x_track/src/App/Utils/Binding/Binding.h b/x_track/src/App/Utils/Binding/Binding.h new file mode 100644 index 0000000000000000000000000000000000000000..7e5a2a718d0cc81326b35f7d32c415fa6167041f --- /dev/null +++ b/x_track/src/App/Utils/Binding/Binding.h @@ -0,0 +1,87 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __BINDING_H +#define __BINDING_H + +template <class T, class CONTEXT> +class Binding { +public: + typedef void (*SET_FUNC)(CONTEXT*, T); + typedef T (*GET_FUNC)(CONTEXT*); + +public: + Binding(T v, SET_FUNC setFunc, GET_FUNC getFunc, CONTEXT* ctx) + { + Binding(setFunc, getFunc, ctx); + set(v); + } + + Binding(SET_FUNC setFunc, GET_FUNC getFunc, CONTEXT* ctx) + : _setFunc(setFunc) + , _getFunc(getFunc) + , _ctx(ctx) + { + } + + Binding() + : _setFunc(nullptr) + , _getFunc(nullptr) + , _ctx(nullptr) + { + } + + void setCallback(SET_FUNC setFunc, GET_FUNC getFunc, CONTEXT* ctx) + { + _setFunc = setFunc; + _getFunc = getFunc; + _ctx = ctx; + } + + void set(T v) + { + _setFunc(_ctx, v); + } + + T get() const + { + return _getFunc(_ctx); + } + + operator T() const + { + return get(); + } + + Binding& operator=(T v) + { + set(v); + return *this; + } + +private: + SET_FUNC _setFunc; + GET_FUNC _getFunc; + CONTEXT* _ctx; +}; + +#endif /* __BINDING_H */ diff --git a/x_track/src/App/Utils/CommonMacro/CommonMacro.h b/x_track/src/App/Utils/CommonMacro/CommonMacro.h new file mode 100644 index 0000000000000000000000000000000000000000..a9554920e7c1c59684182183ec3192b856518c28 --- /dev/null +++ b/x_track/src/App/Utils/CommonMacro/CommonMacro.h @@ -0,0 +1,209 @@ +/** + * @file CommonMacro.h + * @author _VIFEXTech (vifextech@foxmail.com) + * @brief General macro definition method library + * @version 1.8 + * @date 2023-02-04 + * + * @copyright Copyright (c) 2023 + * + */ +#ifndef __COMMON_MACRO_H +#define __COMMON_MACRO_H + +// 2019-03-21 v1.0 Add notes +// 2019-03-21 v1.1 Add __ValueCloseTo +// 2019-05-16 v1.2 Add __ExecuteexprWithTimeout +// 2019-05-16 v1.3 Add __ValueStep +// 2019-09-25 v1.4 Add __ExecuteOnce +// 2020-01-27 v1.5 Add __SemaphoreTake +// 2020-03-10 v1.6 Add __ValuePlus +// 2022-03-14 v1.7 Rename APIs: CM_XXX; __Sizeof -> __ARRAY_SIZE; Add __EventMonitor +// 2023-02-04 v1.8 Refactoring; use English comments + +#ifndef CM_TICK_GET +#ifdef ARDUINO +#define CM_TICK_GET() millis() +#endif +#endif + +/** + * @brief Variable Watcher, which fires an event when a variable changes + * @param now: monitored variables (integer) + * @param expr: an expression triggered by an event + */ +#define CM_VALUE_MONITOR(now, expr) \ + do { \ + static int last = (now); \ + if (last != (now)) { \ + expr; \ + last = (now); \ + } \ + } while (0) + +/** + * @brief Make a variable approach a specified value in designed steps + * @param src: controlled variable + * @param dest: value being approximated + * @param step: step length + */ +#define CM_VALUE_CLOSE_TO(src, dest, step) \ + do { \ + if ((src) < (dest)) { \ + (src) += (step); \ + } else if ((src) > (dest)) { \ + (src) -= (step); \ + } \ + } while (0) + +/** + * @brief Let a variable increase or subtract a value, + * starting from 0 when it is greater than or equal to the maximum value, + * and starting from the maximum value after being lower than 0 + * @param src: controlled variable. + * @param step: step length + * @param max: maximum value + */ +#define CM_VALUE_STEP(src, step, max) \ + ((src) = (((step) >= 0) \ + ? (((src) + (step)) % (max)) \ + : (((src) + (max) + (step)) % (max)))) + +/** + * @brief Let a variable increase or decrease a value, + * start from the minimum value after it is greater than the maximum value, + * and start from the maximum value after it is less than the minimum value + * @param src: controlled variable + * @param plus: value added + * @param min: minimum value + * @param max: maximum value + */ +#define CM_VALUE_PLUS(src, plus, min, max) \ + do { \ + int cm_tmp_value = (src); \ + cm_tmp_value += (plus); \ + if (cm_tmp_value < (min)) { \ + cm_tmp_value = (max); \ + } else if (cm_tmp_value > (max)) { \ + cm_tmp_value = (min); \ + } \ + (src) = cm_tmp_value; \ + } while (0) + +/** + * @brief Get constraint value + * @param x: original value + * @param min: minimum value + * @param max: maximum value + * @retval constraint value + */ +#define CM_CONSTRAIN(x, low, high) ((x) < (low) ? (low) : ((x) > (high) ? (high) : (x))) + +/** + * @brief Restrict a value to a range + * @param x: original value + * @param min: minimum value + * @param max: maximum value + */ +#define CM_VALUE_LIMIT(x, min, max) ((x) = CM_CONSTRAIN((x), (min), (max))) + +/** + * @brief Linearly maps a range of changes in one value to another + * @param x: mapped value + * @param in_min: the minimum value to be mapped + * @param in_max: the maximum value to be mapped + * @param out_min: the minimum value of the map output + * @param out_max: the maximum value of the map output + * @retval mapped value + */ +#define CM_VALUE_MAP(x, in_min, in_max, out_min, out_max) \ + (((x) - (in_min)) * ((out_max) - (out_min)) / ((in_max) - (in_min)) + (out_min)) + +/** + * @brief Non-blocking execution of an expression at specified time intervals + * @param expr: executed expression + * @param time: time interval + */ +#define CM_EXECUTE_INTERVAL(expr, time) \ + do { \ + static unsigned long lasttime = 0; \ + if (CM_TICK_GET() - lasttime >= (time)) { \ + expr; \ + lasttime = CM_TICK_GET(); \ + }; \ + } while (0) + +/** + * @brief Repeated call expression + * @param expr: executed expression + * @param n: number of calls + */ +#define CM_EXECUTE_LOOP(expr, n) \ + do { \ + for (unsigned long i = 0; i < (n); i++) { \ + expr; \ + } \ + } while (0) + +/** + * @brief ִExecutes an expression until the exprtion returns + * the specified value without timing out + * @param expr: executed expression + * @param retval: expected exprtion return value + * @param timeout: time out + * @param is_timeout: Provide variables externally to check whether timeout + */ +#define CM_EXECUTE_WITH_TIMEOUT(expr, n, timeout, is_timeout) \ + do { \ + unsigned long start = CM_TICK_GET(); \ + (is_timeout) = false; \ + while (CM_TICK_GET() - start < (timeout)) { \ + if (expr == (n)) { \ + (is_timeout) = true; \ + break; \ + } \ + } \ + } while (0) + +/** + * @brief Execute the expression only once + * @param expr: executed expression + */ +#define CM_EXECUTE_ONCE(expr) \ + do { \ + static bool cm_is_inited = false; \ + if (!cm_is_inited) { \ + expr; \ + cm_is_inited = true; \ + } \ + } while (0) + +/** + * @brief Get the semaphore and execute the expression once when sem is true + * @param sem: semaphore (bool) + * @param expr: executed expression + */ +#define CM_EXECUTE_SEM_TAKE(sem, expr) \ + do { \ + if ((sem)) { \ + expr; \ + (sem) = false; \ + } \ + } while (0) + +/** + * @brief Get the number of elements in the array + * @param arr: array + * @retval the number of elements in the array + */ +#define CM_ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) + +/** + * @brief Force conversion of a value, often used for structure copy + * @param type: cast type + * @param data: data being cast + * @retval transformed data + */ +#define CM_TYPE_CAST(type, data) (*((type*)(&(data)))) + +#endif diff --git a/x_track/src/App/Utils/FifoQueue/FifoQueue.h b/x_track/src/App/Utils/FifoQueue/FifoQueue.h new file mode 100644 index 0000000000000000000000000000000000000000..17a13ee4694a3c5518c6e3207ecd84790ab9f01c --- /dev/null +++ b/x_track/src/App/Utils/FifoQueue/FifoQueue.h @@ -0,0 +1,90 @@ +/* + * MIT License + * Copyright (c) 2020 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __FIFO_QUEUE_H +#define __FIFO_QUEUE_H + +#include <cstddef> + +template <class T, size_t SIZE> +class FifoQueue { +public: + FifoQueue() + : _head(0) + , _tail(0) {}; + + size_t available() + { + return (SIZE + _head - _tail) % SIZE; + } + + size_t size() + { + return SIZE - 1; + } + + bool full() + { + return (available() == size()); + } + + bool empty() + { + return (_head == _tail); + } + + bool write(const T& data) + { + size_t i = (_head + 1) % SIZE; + + if (i == _tail) { + return false; + } + + _buffer[_head] = data; + _head = i; + return true; + } + + bool read(T& data) + { + if (empty()) { + return false; + } + + data = _buffer[_tail]; + _tail = (_tail + 1) % SIZE; + return true; + } + + void flush() + { + _head = _tail; + } + +private: + T _buffer[SIZE]; + size_t _head; + size_t _tail; +}; + +#endif /* __FIFO_QUEUE_H */ diff --git a/x_track/src/App/Utils/Filters/FilterBase.h b/x_track/src/App/Utils/Filters/FilterBase.h new file mode 100644 index 0000000000000000000000000000000000000000..2f344473c810dc1a409a5fc8825cc6e1c45cebc6 --- /dev/null +++ b/x_track/src/App/Utils/Filters/FilterBase.h @@ -0,0 +1,61 @@ +/* + * MIT License + * Copyright (c) 2021 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __FILTER_BASE_H +#define __FILTER_BASE_H + +#include <stdint.h> + +#define FILTER_ABS(x) (((x)>0)?(x):-(x)) + +namespace Filter +{ + +template <class T> class Base +{ +public: + virtual void Reset() + { + isFirst = true; + } + + virtual bool CheckFirst() + { + bool retval = isFirst; + isFirst = false; + return retval; + } + + virtual T GetNext(T value) + { + lastValue = value; + return lastValue; + } + +protected: + T lastValue; + bool isFirst; +}; + +} + +#endif // ! __FILTER_BASE_H diff --git a/x_track/src/App/Utils/Filters/Filters.h b/x_track/src/App/Utils/Filters/Filters.h new file mode 100644 index 0000000000000000000000000000000000000000..dec45d606c21b0e70ab8b287c35b8b15b738fbf9 --- /dev/null +++ b/x_track/src/App/Utils/Filters/Filters.h @@ -0,0 +1,32 @@ +/* + * MIT License + * Copyright (c) 2021 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __FILTERS_H +#define __FILTERS_H + +#include "HysteresisFilter.h" +#include "LowpassFilter.h" +#include "MedianFilter.h" +#include "MedianQueueFilter.h" +#include "SlidingFilter.h" + +#endif diff --git a/x_track/src/App/Utils/Filters/HysteresisFilter.h b/x_track/src/App/Utils/Filters/HysteresisFilter.h new file mode 100644 index 0000000000000000000000000000000000000000..68ffe304e026487066940f4d30a4c8c41ca447b2 --- /dev/null +++ b/x_track/src/App/Utils/Filters/HysteresisFilter.h @@ -0,0 +1,55 @@ +/* + * MIT License + * Copyright (c) 2021 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __HYSTERESIS_FILTER_H +#define __HYSTERESIS_FILTER_H + +#include "FilterBase.h" + +namespace Filter +{ + +template <typename T> class Hysteresis : public Base<T> +{ +public: + Hysteresis(T hysVal) + { + this->Reset(); + this->hysValue = hysVal; + } + + virtual T GetNext(T value) + { + if (FILTER_ABS(value - this->lastValue) > this->hysValue) + { + this->lastValue = value; + } + return this->lastValue; + } + +private: + T hysValue; +}; + +} + +#endif diff --git a/x_track/src/App/Utils/Filters/LowpassFilter.h b/x_track/src/App/Utils/Filters/LowpassFilter.h new file mode 100644 index 0000000000000000000000000000000000000000..77991f6a8406501ee2622d6894505355d0f6036a --- /dev/null +++ b/x_track/src/App/Utils/Filters/LowpassFilter.h @@ -0,0 +1,68 @@ +/* + * MIT License + * Copyright (c) 2021 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __LOWPASS_FILTER_H +#define __LOWPASS_FILTER_H + +#include "FilterBase.h" + +namespace Filter +{ + +template <typename T> class Lowpass : public Base<T> +{ +public: + Lowpass(float dt, float cutoff) + { + this->Reset(); + + if (cutoff > 0.001f) + { + float RC = 1 / (2 * 3.141592653f * cutoff); + this->rc = dt / (RC + dt); + } + else + { + this->rc = 1; + } + } + + virtual T GetNext(T value) + { + if (this->CheckFirst()) + { + return this->lastValue = value; + } + else + { + this->lastValue = (this->lastValue + (value - this->lastValue) * this->rc); + return this->lastValue; + } + } + +private: + float rc; +}; + +} + +#endif diff --git a/x_track/src/App/Utils/Filters/MedianFilter.h b/x_track/src/App/Utils/Filters/MedianFilter.h new file mode 100644 index 0000000000000000000000000000000000000000..a8498db3b14d9e6e9b76043aef7dd45d10a5017e --- /dev/null +++ b/x_track/src/App/Utils/Filters/MedianFilter.h @@ -0,0 +1,79 @@ +/* + * MIT License + * Copyright (c) 2021 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __MEDIAN_FILTER_H +#define __MEDIAN_FILTER_H + +#include "FilterBase.h" +#include <algorithm> + +namespace Filter +{ + +template <typename T, size_t bufferSize> class Median : public Base<T> +{ +public: + Median() + { + this->Reset(); + this->dataIndex = 0; + } + + bool FillBuffer(T value) + { + if (this->dataIndex < bufferSize) + { + this->buffer[this->dataIndex] = value; + this->dataIndex++; + return false; + } + return true; + } + + virtual T GetNext(T value) + { + if (this->isFirst) + { + this->isFirst = !FillBuffer(value); + this->lastValue = value; + } + else + { + if (FillBuffer(value)) + { + std::sort(this->buffer, this->buffer + bufferSize); + this->lastValue = this->buffer[bufferSize / 2]; + this->dataIndex = 0; + } + } + + return this->lastValue; + } + +protected: + T buffer[bufferSize]; + size_t dataIndex; +}; + +} + +#endif diff --git a/x_track/src/App/Utils/Filters/MedianQueueFilter.h b/x_track/src/App/Utils/Filters/MedianQueueFilter.h new file mode 100644 index 0000000000000000000000000000000000000000..7b2ee4dfae4035705be5db08c2fa5f50e50e37b7 --- /dev/null +++ b/x_track/src/App/Utils/Filters/MedianQueueFilter.h @@ -0,0 +1,68 @@ +/* + * MIT License + * Copyright (c) 2021 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __MEDIAN_QUEUE_FILTER_H +#define __MEDIAN_QUEUE_FILTER_H + +#include "MedianFilter.h" + +namespace Filter +{ + +template <typename T, size_t bufferSize> class MedianQueue : public Median<T, bufferSize> +{ +public: + MedianQueue() : Median<T, bufferSize>() + { + } + + virtual T GetNext(T value) + { + if (this->isFirst) + { + this->isFirst = !this->FillBuffer(value); + this->lastValue = value; + } + else + { + this->dataIndex %= bufferSize; + this->buffer[this->dataIndex] = value; + this->dataIndex++; + + for (size_t i = 0; i < bufferSize; i++) + { + this->bufferSort[i] = this->buffer[i]; + } + std::sort(this->bufferSort, this->bufferSort + bufferSize); + this->lastValue = this->bufferSort[bufferSize / 2]; + } + + return this->lastValue; + } + +protected: + T bufferSort[bufferSize]; +}; + +} + +#endif diff --git a/x_track/src/App/Utils/Filters/SlidingFilter.h b/x_track/src/App/Utils/Filters/SlidingFilter.h new file mode 100644 index 0000000000000000000000000000000000000000..6d82870d605ebc18c5e5a9d9dd60f9ae6cf06f8a --- /dev/null +++ b/x_track/src/App/Utils/Filters/SlidingFilter.h @@ -0,0 +1,73 @@ +/* + * MIT License + * Copyright (c) 2021 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __SLIDING_FILTER_H +#define __SLIDING_FILTER_H + +#include "FilterBase.h" + +namespace Filter +{ + +template <typename T> class Sliding : public Base<T> +{ +public: + Sliding(T sldVal) + { + this->Reset(); + this->slideValue = sldVal; + } + + virtual T GetNext(T value) + { + if (this->CheckFirst()) + { + this->lastValue = value; + } + else + { + if (FILTER_ABS(value - this->lastValue) < this->slideValue) + { + this->lastValue = value; + } + else + { + if (this->lastValue < value) + { + this->lastValue += this->slideValue; + } + else if (this->lastValue > value) + { + this->lastValue -= this->slideValue; + } + } + } + return this->lastValue; + } + +private: + T slideValue; +}; + +} + +#endif diff --git a/x_track/src/App/Utils/GPX/Examples/Static_Track/Static_Track.pde b/x_track/src/App/Utils/GPX/Examples/Static_Track/Static_Track.pde new file mode 100644 index 0000000000000000000000000000000000000000..05e99f0d3837027394bbb79bf6af25c9e6fcf2bf --- /dev/null +++ b/x_track/src/App/Utils/GPX/Examples/Static_Track/Static_Track.pde @@ -0,0 +1,29 @@ +#include <GPX.h> + +GPX myGPX; + +void setup() +{ + // start serial port at 9600 bps: + Serial.begin(9600); + String dumbString(""); +} + +void loop(){ + Serial.print(myGPX.getOpen()); + myGPX.setMetaDesc("foofoofoo"); + myGPX.setName("track name"); + myGPX.setDesc("Track description"); + myGPX.setSrc("SUP500Ff"); + Serial.print(myGPX.getMetaData()); + Serial.print(myGPX.getTrakOpen()); + Serial.print(myGPX.getInfo()); + Serial.print(myGPX.getTrakSegOpen()); + Serial.print(myGPX.getPt(GPX_TRKPT,"41.123","-71.456")); + Serial.print(myGPX.getPt(GPX_TRKPT,"42.123","-72.456")); + Serial.print(myGPX.getPt(GPX_TRKPT,"43.123","-73.456")); + Serial.print(myGPX.getTrakSegClose()); + Serial.print(myGPX.getTrakClose()); + Serial.print(myGPX.getClose()); + delay(10000); +} diff --git a/x_track/src/App/Utils/GPX/GPX.cpp b/x_track/src/App/Utils/GPX/GPX.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2365787e17afbd23c6d8dab97fc254eb50145b22 --- /dev/null +++ b/x_track/src/App/Utils/GPX/GPX.cpp @@ -0,0 +1,216 @@ +/* + * GPX Library -- GPX.cpp + * Created by: Ryan M Sutton + * + * Copyright (c) 2010, Ryan M Sutton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Ryan M Sutton nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL Ryan M Sutton BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "GPX.h" + +#define _GPX_HEAD "<gpx version=\"1.1\" creator=\"Arduino GPX Lib\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns=\"http://www.topografix.com/GPX/1/1\"\n xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\"\n>\n" +#define _GPX_TAIL "</gpx>\n" +#define _GPX_META_HEAD "<metadata>" +#define _GPX_META_TAIL "</metadata>\n" +#define _GPX_TRAK_HEAD "<trk>" +#define _GPX_TRAK_TAIL "</trk>\n" +#define _GPX_TRKSEG_HEAD "<trkseg>" +#define _GPX_TRKSEG_TAIL "</trkseg>\n" +#define _GPX_PT_HEAD "<TYPE lat=\"" +#define _GPX_PT_TAIL "</TYPE>\n" + +// Property Tags +#define _GPX_NAME_HEAD "<name>" +#define _GPX_NAME_TAIL "</name>\n" +#define _GPX_DESC_HEAD "<desc>" +#define _GPX_DESC_TAIL "</desc>\n" +#define _GPX_SYM_HEAD "<sym>" +#define _GPX_SYM_TAIL "</sym>\n" +#define _GPX_ELE_HEAD "<ele>" +#define _GPX_ELE_TAIL "</ele>\n" +#define _GPX_SRC_HEAD "<src>" +#define _GPX_SRC_TAIL "</src>\n" +#define _GPX_TIME_HEAD "<time>" +#define _GPX_TIME_TAIL "</time>\n" + +GPX::GPX() +{ +} + +//Get methods + +String GPX::getOpen() +{ + return String(_GPX_HEAD); +} + +String GPX::getClose() +{ + return String(_GPX_TAIL); +} + +String GPX::getMetaData() +{ + String localStr(_GPX_META_HEAD); + if (_metaName.length() > 0) + { + localStr = localStr + String(_GPX_NAME_HEAD); + localStr = localStr + wrapCDATA(_metaName); + localStr = localStr + String(_GPX_NAME_TAIL); + } + if (_metaDesc.length() > 0) + { + localStr = localStr + String(_GPX_DESC_HEAD); + localStr = localStr + wrapCDATA(_metaDesc); + localStr = localStr + String(_GPX_DESC_TAIL); + } + localStr = localStr + String(_GPX_META_TAIL); + return localStr; +} + +String GPX::getTrakOpen() +{ + return String(_GPX_TRAK_HEAD); +} + +String GPX::getTrakClose() +{ + return String(_GPX_TRAK_TAIL); +} + +String GPX::getTrakSegOpen() +{ + return String(_GPX_TRKSEG_HEAD); +} + +String GPX::getTrakSegClose() +{ + return String(_GPX_TRKSEG_TAIL); +} + +String GPX::getInfo() +{ + String localStr(""); + if (_name.length() > 0) + { + localStr += _GPX_NAME_HEAD; + localStr += wrapCDATA(_name); + localStr += _GPX_NAME_TAIL; + } + if (_desc.length() > 0) + { + localStr += _GPX_DESC_HEAD; + localStr += wrapCDATA(_desc); + localStr += _GPX_DESC_TAIL; + } + return localStr; +} + +String GPX::getPt(String typ, String lon, String lat) +{ + String localStr(_GPX_PT_HEAD); + //localStr = localStr.replace("TYPE",typ); + localStr.replace("TYPE", typ); + localStr += lat + "\" lon=\""; + localStr += lon + "\">"; + if (_ele.length() > 0) + { + localStr += _GPX_ELE_HEAD; + localStr += _ele; + localStr += _GPX_ELE_TAIL; + } + if (_time.length() > 0) + { + localStr += _GPX_TIME_HEAD; + localStr += _time; + localStr += _GPX_TIME_TAIL; + } + if (_sym.length() > 0) + { + localStr += _GPX_SYM_HEAD; + localStr += _sym; + localStr += _GPX_SYM_TAIL; + } + if (_src.length() > 0) + { + localStr += _GPX_SRC_HEAD; + localStr += wrapCDATA(_src); + localStr += _GPX_SRC_TAIL; + } + String GPX_PT_TAIL = _GPX_PT_TAIL; + GPX_PT_TAIL.replace("TYPE", typ); + localStr += GPX_PT_TAIL; + return localStr; +} + +String GPX::getPt(String typ, String lon, String lat, String ele) +{ + return getPt(typ, lon, lat); +} + +//Set Methods +void GPX::setMetaName(String name) +{ + _metaName = name; +} +void GPX::setMetaDesc(String desc) +{ + _metaDesc = desc; +} +void GPX::setName(String name) +{ + _name = name; +} +void GPX::setDesc(String desc) +{ + _desc = desc; +} +void GPX::setEle(String ele) +{ + _ele = ele; +} +void GPX::setSym(String sym) +{ + _sym = sym; +} +void GPX::setSrc(String src) +{ + _src = src; +} +void GPX::setTime(String time) +{ + _time = time; +} + +//Private Functions +String GPX::wrapCDATA(String input) +{ + String localStr("<![CDATA["); + localStr += input; + localStr += "]]>"; + + return localStr; +} diff --git a/x_track/src/App/Utils/GPX/GPX.h b/x_track/src/App/Utils/GPX/GPX.h new file mode 100644 index 0000000000000000000000000000000000000000..8b3cf503ca591012bd1761bc361602e5594aaf9a --- /dev/null +++ b/x_track/src/App/Utils/GPX/GPX.h @@ -0,0 +1,83 @@ +/* + * GPX Library -- GPX.h + * Created by: Ryan Sutton + * + * Copyright (c) 2010, Ryan M Sutton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Ryan M Sutton nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GPX_h +#define GPX_h + +#ifdef ARDUINO +#include "WString.h" +#else +#include "../WString/WString.h" +#endif // ARDUINO + +// 'Public' Tags +#define GPX_TRKPT "trkpt" +#define GPX_WPT "wpt" +#define GPX_RTEPT "rtept" + +class GPX +{ +public: + GPX(); + String getOpen(); + String getClose(); + String getMetaData(); + String getTrakOpen(); + String getTrakClose(); + String getTrakSegOpen(); + String getTrakSegClose(); + String getInfo(); + String getPt(String typ, String lon, String lat); + String getPt(String typ, String lon, String lat, String ele); + void setMetaName(String name); + void setMetaDesc(String desc); + void setName(String name); + void setDesc(String desc); + void setEle(String ele); + void setSym(String sym); + void setSrc(String src); + void setTime(String time); +private: + //Variables + String _metaName; + String _metaDesc; + String _name; + String _desc; + String _ele; + String _sym; + String _src; + String _time; + + //Functions + String wrapCDATA(String input); +}; + +#endif + diff --git a/x_track/src/App/Utils/GPX_Parser/GPX_Parser.cpp b/x_track/src/App/Utils/GPX_Parser/GPX_Parser.cpp new file mode 100644 index 0000000000000000000000000000000000000000..31e18ca1604184bb9c1fe8da2cc44fdd2354d893 --- /dev/null +++ b/x_track/src/App/Utils/GPX_Parser/GPX_Parser.cpp @@ -0,0 +1,118 @@ +#include "GPX_Parser.h" +#include <string.h> + +GPX_Parser::GPX_Parser(Callback_t avaliableCallback, Callback_t readCallback, void* userData) + : _avaliableCallback(avaliableCallback) + , _readCallback(readCallback) + , _userData(userData) +{ +} + +bool GPX_Parser::readStringUntil(char terminator, String* str) +{ + bool retval = false; + char strBuf[64]; + + size_t len = readBytesUntil(terminator, strBuf, sizeof(strBuf)); + + if (len < sizeof(strBuf)) { + strBuf[len] = '\0'; + *str = strBuf; + retval = true; + } + + return retval; +} + +int GPX_Parser::getNext(Point_t* point) +{ + String str; + int flag = FLAG_NONE; + + if (!find((char*)"<trkpt")) { + flag |= FLAG_UNMATCHED; + flag |= FLAG_END_OF_FILE; + goto failed; + } + + while (true) { + bool ret = readStringUntil('>', &str); + if (!ret) { + flag |= FLAG_UNMATCHED; + goto failed; + } + + int index = str.indexOf("lat="); + if (index >= 0) { + String lat = str.substring(str.indexOf('"') + 1); + point->latitude = lat.toFloat(); + flag |= FLAG_LATITUDE; + + String lon = str.substring(str.indexOf("lon=")); + lon = lon.substring(lon.indexOf('"') + 1); + point->longitude = lon.toFloat(); + flag |= FLAG_LONGITUDE; + continue; + } + + index = str.indexOf("<ele"); + if (index >= 0) { + if (!readStringUntil('>', &str)) { + flag |= FLAG_UNMATCHED; + goto failed; + } + String ele = str; + point->altitude = ele.toFloat(); + flag |= FLAG_ALTITUDE; + continue; + } + + index = str.indexOf("<time"); + if (index >= 0) { + if (!readStringUntil('>', &str)) { + flag |= FLAG_UNMATCHED; + goto failed; + } + String time = str; + int year, month, day, hour, minute, second; + sscanf( + time.c_str(), + "%d-%d-%dT%d:%d:%dZ", + &year, + &month, + &day, + &hour, + &minute, + &second); + point->time.year = year; + point->time.month = month; + point->time.day = day; + point->time.hour = hour; + point->time.minute = minute; + point->time.second = second; + flag |= FLAG_TIME; + continue; + } + + if (str.length() == 0) { + goto failed; + } + + if (str.indexOf("</trkpt") >= 0) { + break; + } + } + +failed: + return flag; +} + +int GPX_Parser::available() +{ + return _avaliableCallback(this); +} + +int GPX_Parser::read() +{ + return _readCallback(this); +} diff --git a/x_track/src/App/Utils/GPX_Parser/GPX_Parser.h b/x_track/src/App/Utils/GPX_Parser/GPX_Parser.h new file mode 100644 index 0000000000000000000000000000000000000000..28dcc687dd5b6f0af1c9b9e7084e4bb32764a679 --- /dev/null +++ b/x_track/src/App/Utils/GPX_Parser/GPX_Parser.h @@ -0,0 +1,74 @@ +#ifndef __GPX_PARSER_H +#define __GPX_PARSER_H + +#ifdef ARDUINO +#include "Stream.h" +#else +#include "../Stream/Stream.h" +#endif /* ARDUINO */ + +class GPX_Parser : public Stream { +public: + typedef int (*Callback_t)(GPX_Parser* parser); + + typedef struct { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + } Time_t; + + typedef struct + { + float longitude; + float latitude; + float altitude; + Time_t time; + } Point_t; + + typedef enum { + FLAG_NONE = 0, + + /* Error */ + FLAG_END_OF_FILE = 1 << 0, + FLAG_OVER_FLOW = 1 << 1, + FLAG_UNMATCHED = 1 << 2, + FLAG_RESERVE = 1 << 3, + + /* Data ready */ + FLAG_LONGITUDE = 1 << 4, + FLAG_LATITUDE = 1 << 5, + FLAG_ALTITUDE = 1 << 6, + FLAG_TIME = 1 << 7 + } Flag_t; + +public: + GPX_Parser(Callback_t avaliableCallback, Callback_t readCallback, void* userData); + int getNext(Point_t* point); + void* getUserData() { return _userData; } + +private: + virtual int available(); + virtual int read(); + virtual int peek() + { + return 0; + } + virtual void flush() { } + virtual size_t write(uint8_t ch) + { + return 0; + } + using Print::write; + + bool readStringUntil(char terminator, String* str); + +private: + Callback_t _avaliableCallback; + Callback_t _readCallback; + void* _userData; +}; + +#endif diff --git a/x_track/src/App/Utils/Geo/Geo.cpp b/x_track/src/App/Utils/Geo/Geo.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9238a09a183cbd67bd638f87b54963e48bae43f5 --- /dev/null +++ b/x_track/src/App/Utils/Geo/Geo.cpp @@ -0,0 +1,120 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "Geo.h" +#include <cfloat> +#include <cmath> + +/* clang-format off */ + +#define TWO_PI (2.0 * M_PI) +#define DEG_TO_RAD 0.017453292519943295769236907684886 +#define RAD_TO_DEG 57.295779513082320876798154814105 +#define RADIANS(deg) ((deg) * DEG_TO_RAD) +#define DEGREES(rad) ((rad) * RAD_TO_DEG) +#define SQ(x) ((x) * (x)) + +/* clang-format on */ + +static inline bool math_zero(float a) +{ + return (fabsf(a) < FLT_EPSILON); +} + +double Geo::distanceBetween(double lat1, double long1, double lat2, double long2) +{ + /* returns distance in meters between two positions, both specified + * as signed decimal-DEGREES latitude and longitude. Uses great-circle + * distance computation for hypothetical sphere of radius 6372795 meters. + * Because Earth is no exact sphere, rounding errors may be up to 0.5%. + * Courtesy of Maarten Lamers + */ + double delta = RADIANS(long1 - long2); + double sdlong = sin(delta); + double cdlong = cos(delta); + lat1 = RADIANS(lat1); + lat2 = RADIANS(lat2); + double slat1 = sin(lat1); + double clat1 = cos(lat1); + double slat2 = sin(lat2); + double clat2 = cos(lat2); + delta = (clat1 * slat2) - (slat1 * clat2 * cdlong); + delta = SQ(delta); + delta += SQ(clat2 * sdlong); + delta = sqrt(delta); + double denom = (slat1 * slat2) + (clat1 * clat2 * cdlong); + delta = atan2(delta, denom); + return delta * 6372795; +} + +double Geo::courseTo(double lat1, double long1, double lat2, double long2) +{ + /* returns course in DEGREES (North=0, West=270) from position 1 to position 2, + * both specified as signed decimal-DEGREES latitude and longitude. + * Because Earth is no exact sphere, calculated course may be off by a tiny fraction. + * Courtesy of Maarten Lamers + */ + double dlon = RADIANS(long2 - long1); + lat1 = RADIANS(lat1); + lat2 = RADIANS(lat2); + double a1 = sin(dlon) * cos(lat2); + double a2 = sin(lat1) * cos(lat2) * cos(dlon); + a2 = cos(lat1) * sin(lat2) - a2; + a2 = atan2(a1, a2); + if (a2 < 0.0) { + a2 += TWO_PI; + } + + return DEGREES(a2); +} + +const char* Geo::cardinal(float course) +{ + static const char* directions[] = { "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW" }; + int direction = (int)((course + 11.25f) / 22.5f); + return directions[direction % 16]; +} + +float Geo::grade(float height, float distance) +{ + if (math_zero(distance)) { + return 0; + } + + return (height / distance) * 100.0f; +} + +float Geo::slope(float height, float distance) +{ + return DEGREES(atan2f(height, distance)); +} + +float Geo::sealevel(float pressure, float altitude) +{ + return (pressure / powf(1 - (altitude / 44330.0f), 5.255f)); +} + +float Geo::pressureToAltitude(float pressure, float sealevel) +{ + return (44330.0f * (1 - powf(pressure / sealevel, 1 / 5.255f))); +} diff --git a/x_track/src/App/Utils/Geo/Geo.h b/x_track/src/App/Utils/Geo/Geo.h new file mode 100644 index 0000000000000000000000000000000000000000..0c6167ab90e8a137d0b4bbccc21ad1731707871f --- /dev/null +++ b/x_track/src/App/Utils/Geo/Geo.h @@ -0,0 +1,116 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __GEO_H +#define __GEO_H + +namespace Geo { + +/** + * @brief Calculate the distance between two points on the Earth's surface. + * @param lat1 Latitude of the first point in degrees. + * @param long1 Longitude of the first point in degrees. + * @param lat2 Latitude of the second point in degrees. + * @param long2 Longitude of the second point in degrees. + * @return Distance between the two points in meters. + */ +double distanceBetween(double lat1, double long1, double lat2, double long2); + +/** + * @brief Calculate the course to reach the second point from the first point. + * @param lat1 Latitude of the first point in degrees. + * @param long1 Longitude of the first point in degrees. + * @param lat2 Latitude of the second point in degrees. + * @param long2 Longitude of the second point in degrees. + * @return Course to reach the second point from the first point in degrees. + */ +double courseTo(double lat1, double long1, double lat2, double long2); + +/** + * @brief Calculate the cardinal direction of the course. + * @param course Course in degrees. + * @return Cardinal direction of the course. + */ +const char* cardinal(float course); + +/** + * @brief Calculate the grade of the slope. + * @param height Height of the slope in meters. + * @param distance Distance between the two points on the slope in meters. + * @return Grade of the slope in percent. + * @note The formula used is: grade = (height / distance) * 100. + * @note If the distance is zero, the function returns zero. +*/ +float grade(float height, float distance); + +/** + * @brief Calculate the slope of the line. + * @param height Height of the slope in meters. + * @param distance Distance between the two points on the slope in meters. + * @return Slope of the line in degrees. + * @note The formula used is: slope = atan2f(height, distance) * 180 / pi. + * @note If the distance is zero, the function returns zero. + */ +float slope(float height, float distance); + +/** + * @brief Given a pressure (mb) taken at a specific altitude (meters), + * calculate the equivalent pressure (mb) at sea level. + * This produces pressure readings that can be used for weather measurements. + * @param pressure Pressure (mb) taken at a specific altitude (meters). + * @param altitude Altitude (meters) at which the pressure was taken. + * @return Equivalent pressure (mb) at sea level. + */ +float sealevel(float pressure, float altitude); + +/** + * @brief Given a pressure (mb) and the pressure at sea level (mb), + * return altitude (meters) above sea level. + * @param pressure Pressure (mb) taken at a specific altitude (meters). + * @param sealevel Pressure (mb) at sea level. + * @return Altitude (meters) above sea level. + */ +float pressureToAltitude(float pressure, float sealevel); + +/** + * @brief Convert kilopascals (kPa) to millibars (mb). + * @param kpa Pressure in kilopascals. + * @return Pressure in millibars. + */ +static inline float kpaTombar(float kpa) +{ + return kpa * 10.0f; +} + +/** + * @brief Convert millibars (mb) to kilopascals (kPa). + * @param mb Pressure in millibars. + * @return Pressure in kilopascals. + */ +static inline float mbarTokpa(float mb) +{ + return mb / 10.0f; +} + +} // namespace Geo + +#endif // __GEO_H diff --git a/x_track/src/App/Utils/MapConv/MapConv.cpp b/x_track/src/App/Utils/MapConv/MapConv.cpp new file mode 100644 index 0000000000000000000000000000000000000000..115d5c08d2d47e9f210270314d49d57993a22e85 --- /dev/null +++ b/x_track/src/App/Utils/MapConv/MapConv.cpp @@ -0,0 +1,120 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "MapConv.h" +#include "TileSystem.h" +#include <cmath> +#include <cstdio> + +#define LONGITUDE_OFFSET 0.008 +#define LATITUDE_OFFSET 0.009 + +using namespace ::Microsoft_MapPoint; + +MapConv::MapConv(bool coordTransformEnable, int level, int tileSize) + : _longitudeOffset(0) + , _latitudeOffset(0) + , _longitudeLast(0) + , _latitudeLast(0) + , _level(level) + , _tileSize(tileSize) + , _coordTransformEnable(coordTransformEnable) +{ +} + +void MapConv::setCoordTransformEnable(bool enable) +{ + _coordTransformEnable = enable; +} + +bool MapConv::getCoordTransformEnable() +{ + return _coordTransformEnable; +} + +void MapConv::setLevel(int level) +{ + _level = level; +} + +int MapConv::getLevel() +{ + return _level; +} + +MapConv::Tile_t MapConv::getTile(double longitude, double latitude) +{ + Point_t point = getCoordinate(longitude, latitude); + return posToTile(point.x, point.y); +} + +MapConv::Point_t MapConv::getCoordinate(double longitude, double latitude) +{ + int pixelX, pixelY; + + TileSystem::LatLongToPixelXY( + latitude, + longitude, + _level, + &pixelX, + &pixelY); + + Point_t point; + point.x = pixelX; + point.y = pixelY; + return point; +}; + +int MapConv::getPath(char* path, size_t len, int32_t x, int32_t y) +{ + int tileX = x / _tileSize; + int tileY = y / _tileSize; + int ret = snprintf(path, len, "/%d/%d/%d", _level, tileX, tileY); + return ret; +} + +MapConv::Tile_t MapConv::posToTile(int32_t x, int32_t y) +{ + Tile_t mapTile; + mapTile.tileX = x / _tileSize; + mapTile.tileY = y / _tileSize; + mapTile.subX = x % _tileSize; + mapTile.subY = y % _tileSize; + return mapTile; +} + +MapConv::Point_t MapConv::convertPoint(int32_t x, int32_t y, int level) +{ + Point_t point; + + int diff = level - _level; + + if (diff >= 0) { + point.x = x >> diff; + point.y = y >> diff; + } else { + point.x = x << -diff; + point.y = y << -diff; + } + + return point; +} diff --git a/x_track/src/App/Utils/MapConv/MapConv.h b/x_track/src/App/Utils/MapConv/MapConv.h new file mode 100644 index 0000000000000000000000000000000000000000..b30ffe3bef4a69474cba2626a505c1fd1a03b419 --- /dev/null +++ b/x_track/src/App/Utils/MapConv/MapConv.h @@ -0,0 +1,69 @@ +/* + * MIT License + * Copyright (c) 2021 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __MAP_CONV_H +#define __MAP_CONV_H + +#include <stddef.h> +#include <stdint.h> + +class MapConv { +public: + typedef struct + { + uint32_t tileX; + uint32_t tileY; + uint32_t subX; + uint32_t subY; + } Tile_t; + + typedef struct + { + int32_t x; + int32_t y; + } Point_t; + +public: + MapConv(bool coordTransformEnable, int level, int tileSize = 256); + ~MapConv() { } + + void setCoordTransformEnable(bool enable); + bool getCoordTransformEnable(); + void setLevel(int level); + int getLevel(); + Tile_t getTile(double longitude, double latitude); + int getPath(char* path, size_t len, int32_t x, int32_t y); + Point_t getCoordinate(double longitude, double latitude); + Tile_t posToTile(int32_t x, int32_t y); + Point_t convertPoint(int32_t x, int32_t y, int level); + +private: + double _longitudeOffset; + double _latitudeOffset; + double _longitudeLast; + double _latitudeLast; + int _level; + int _tileSize; + bool _coordTransformEnable; +}; + +#endif diff --git a/x_track/src/App/Utils/MapConv/TileSystem.cpp b/x_track/src/App/Utils/MapConv/TileSystem.cpp new file mode 100644 index 0000000000000000000000000000000000000000..35d090b585152ece1d5e84ab726b10380358ca1f --- /dev/null +++ b/x_track/src/App/Utils/MapConv/TileSystem.cpp @@ -0,0 +1,140 @@ +#include "TileSystem.h" + +#include <algorithm> +#include <math.h> +#include <string.h> + +using namespace Microsoft_MapPoint; + +static const double EarthRadius = 6378137; +static const double MinLatitude = -85.05112878; +static const double MaxLatitude = 85.05112878; +static const double MinLongitude = -180; +static const double MaxLongitude = 180; +static const double MATH_PI = 3.1415926535897932384626433832795; + +/// <summary> +/// Clips a number to the specified minimum and maximum values. +/// </summary> +/// <param name="n">The number to clip.</param> +/// <param name="minValue">Minimum allowable value.</param> +/// <param name="maxValue">Maximum allowable value.</param> +/// <returns>The clipped value.</returns> +static double Clip(double n, double minValue, double maxValue) +{ + return std::min(std::max(n, minValue), maxValue); +} + + +uint32_t TileSystem::MapSize(int levelOfDetail) +{ + return (uint32_t)256 << levelOfDetail; +} + +double TileSystem::GroundResolution(double latitude, int levelOfDetail) +{ + latitude = Clip(latitude, MinLatitude, MaxLatitude); + return cos(latitude * MATH_PI / 180) * 2 * MATH_PI * EarthRadius / MapSize(levelOfDetail); +} + +double TileSystem::MapScale(double latitude, int levelOfDetail, int screenDpi) +{ + return GroundResolution(latitude, levelOfDetail) * screenDpi / 0.0254; +} + +void TileSystem::LatLongToPixelXY(double latitude, double longitude, int levelOfDetail, int* pixelX, int* pixelY) +{ + latitude = Clip(latitude, MinLatitude, MaxLatitude); + longitude = Clip(longitude, MinLongitude, MaxLongitude); + + double x = (longitude + 180) / 360; + double sinLatitude = sin(latitude * MATH_PI / 180); + double y = 0.5 - log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * MATH_PI); + + uint32_t mapSize = MapSize(levelOfDetail); + *pixelX = (int)Clip(x * mapSize + 0.5, 0, mapSize - 1); + *pixelY = (int)Clip(y * mapSize + 0.5, 0, mapSize - 1); +} + +void TileSystem::PixelXYToLatLong(int pixelX, int pixelY, int levelOfDetail, double* latitude, double* longitude) +{ + double mapSize = MapSize(levelOfDetail); + double x = (Clip(pixelX, 0, mapSize - 1) / mapSize) - 0.5; + double y = 0.5 - (Clip(pixelY, 0, mapSize - 1) / mapSize); + + *latitude = 90 - 360 * atan(exp(-y * 2 * MATH_PI)) / MATH_PI; + *longitude = 360 * x; +} + +void TileSystem::PixelXYToTileXY(int pixelX, int pixelY, int* tileX, int* tileY) +{ + *tileX = pixelX / 256; + *tileY = pixelY / 256; +} + +void TileSystem::TileXYToPixelXY(int tileX, int tileY, int* pixelX, int* pixelY) +{ + *pixelX = tileX * 256; + *pixelY = tileY * 256; +} + +void TileSystem::TileXYToQuadKey(int tileX, int tileY, int levelOfDetail, char* quadKeyBuffer, uint32_t len) +{ + uint32_t quadKeyIndex = 0; + for (int i = levelOfDetail; i > 0; i--) + { + char digit = '0'; + int mask = 1 << (i - 1); + if ((tileX & mask) != 0) + { + digit++; + } + if ((tileY & mask) != 0) + { + digit++; + digit++; + } + quadKeyBuffer[quadKeyIndex] = digit; + quadKeyIndex++; + + if (quadKeyIndex >= len - 1) + { + break; + } + } + + quadKeyBuffer[quadKeyIndex] = '\0'; +} + +void TileSystem::QuadKeyToTileXY(const char* quadKey, int* tileX, int* tileY, int* levelOfDetail) +{ + *tileX = *tileY = 0; + int len = (int)strlen(quadKey); + *levelOfDetail = len; + for (int i = len; i > 0; i--) + { + int mask = 1 << (i - 1); + switch (quadKey[len - i]) + { + case '0': + break; + + case '1': + *tileX |= mask; + break; + + case '2': + *tileY |= mask; + break; + + case '3': + *tileX |= mask; + *tileY |= mask; + break; + + default: + //throw new ArgumentException("Invalid QuadKey digit sequence."); + break; + } + } +} diff --git a/x_track/src/App/Utils/MapConv/TileSystem.h b/x_track/src/App/Utils/MapConv/TileSystem.h new file mode 100644 index 0000000000000000000000000000000000000000..b635d830cd778da71b989f17c307ecd79383d2a0 --- /dev/null +++ b/x_track/src/App/Utils/MapConv/TileSystem.h @@ -0,0 +1,118 @@ +//------------------------------------------------------------------------------ +// <copyright company="Microsoft"> +// Copyright (c) 2006-2009 Microsoft Corporation. All rights reserved. +// </copyright> +//------------------------------------------------------------------------------ + +// Reference Link: https://docs.microsoft.com/en-us/bingmaps/articles/bing-maps-tile-system?redirectedfrom=MSDN + +#ifndef __MICROSOFT_MAP_POINT_TILE_SYSTEM_H +#define __MICROSOFT_MAP_POINT_TILE_SYSTEM_H + +#include <stdint.h> + +namespace Microsoft_MapPoint +{ + +namespace TileSystem +{ + +/// <summary> +/// Determines the map width and height (in pixels) at a specified level +/// of detail. +/// </summary> +/// <param name="levelOfDetail">Level of detail, from 1 (lowest detail) +/// to 23 (highest detail).</param> +/// <returns>The map width and height in pixels.</returns> +uint32_t MapSize(int levelOfDetail); + +/// <summary> +/// Determines the ground resolution (in meters per pixel) at a specified +/// latitude and level of detail. +/// </summary> +/// <param name="latitude">Latitude (in degrees) at which to measure the +/// ground resolution.</param> +/// <param name="levelOfDetail">Level of detail, from 1 (lowest detail) +/// to 23 (highest detail).</param> +/// <returns>The ground resolution, in meters per pixel.</returns> +double GroundResolution(double latitude, int levelOfDetail); + +/// <summary> +/// Determines the map scale at a specified latitude, level of detail, +/// and screen resolution. +/// </summary> +/// <param name="latitude">Latitude (in degrees) at which to measure the +/// map scale.</param> +/// <param name="levelOfDetail">Level of detail, from 1 (lowest detail) +/// to 23 (highest detail).</param> +/// <param name="screenDpi">Resolution of the screen, in dots per inch.</param> +/// <returns>The map scale, expressed as the denominator N of the ratio 1 : N.</returns> +double MapScale(double latitude, int levelOfDetail, int screenDpi); + +/// <summary> +/// Converts a point from latitude/longitude WGS-84 coordinates (in degrees) +/// into pixel XY coordinates at a specified level of detail. +/// </summary> +/// <param name="latitude">Latitude of the point, in degrees.</param> +/// <param name="longitude">Longitude of the point, in degrees.</param> +/// <param name="levelOfDetail">Level of detail, from 1 (lowest detail) +/// to 23 (highest detail).</param> +/// <param name="pixelX">Output parameter receiving the X coordinate in pixels.</param> +/// <param name="pixelY">Output parameter receiving the Y coordinate in pixels.</param> +void LatLongToPixelXY(double latitude, double longitude, int levelOfDetail, int* pixelX, int* pixelY); + +/// <summary> +/// Converts a pixel from pixel XY coordinates at a specified level of detail +/// into latitude/longitude WGS-84 coordinates (in degrees). +/// </summary> +/// <param name="pixelX">X coordinate of the point, in pixels.</param> +/// <param name="pixelY">Y coordinates of the point, in pixels.</param> +/// <param name="levelOfDetail">Level of detail, from 1 (lowest detail) +/// to 23 (highest detail).</param> +/// <param name="latitude">Output parameter receiving the latitude in degrees.</param> +/// <param name="longitude">Output parameter receiving the longitude in degrees.</param> +void PixelXYToLatLong(int pixelX, int pixelY, int levelOfDetail, double* latitude, double* longitude); + +/// <summary> +/// Converts pixel XY coordinates into tile XY coordinates of the tile containing +/// the specified pixel. +/// </summary> +/// <param name="pixelX">Pixel X coordinate.</param> +/// <param name="pixelY">Pixel Y coordinate.</param> +/// <param name="tileX">Output parameter receiving the tile X coordinate.</param> +/// <param name="tileY">Output parameter receiving the tile Y coordinate.</param> +void PixelXYToTileXY(int pixelX, int pixelY, int* tileX, int* tileY); + +/// <summary> +/// Converts tile XY coordinates into pixel XY coordinates of the upper-left pixel +/// of the specified tile. +/// </summary> +/// <param name="tileX">Tile X coordinate.</param> +/// <param name="tileY">Tile Y coordinate.</param> +/// <param name="pixelX">Output parameter receiving the pixel X coordinate.</param> +/// <param name="pixelY">Output parameter receiving the pixel Y coordinate.</param> +void TileXYToPixelXY(int tileX, int tileY, int* pixelX, int* pixelY); + +/// <summary> +/// Converts tile XY coordinates into a QuadKey at a specified level of detail. +/// </summary> +/// <param name="tileX">Tile X coordinate.</param> +/// <param name="tileY">Tile Y coordinate.</param> +/// <param name="levelOfDetail">Level of detail, from 1 (lowest detail) +/// to 23 (highest detail).</param> +/// <returns>A string containing the QuadKey.</returns> +void TileXYToQuadKey(int tileX, int tileY, int levelOfDetail, char* quadKeyBuffer, uint32_t len); + +/// <summary> +/// Converts a QuadKey into tile XY coordinates. +/// </summary> +/// <param name="quadKey">QuadKey of the tile.</param> +/// <param name="tileX">Output parameter receiving the tile X coordinate.</param> +/// <param name="tileY">Output parameter receiving the tile Y coordinate.</param> +/// <param name="levelOfDetail">Output parameter receiving the level of detail.</param> +void QuadKeyToTileXY(const char* quadKey, int* tileX, int* tileY, int* levelOfDetail); +} + +} + +#endif diff --git a/x_track/src/App/Utils/MapView/MapView.cpp b/x_track/src/App/Utils/MapView/MapView.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9ad21c8029f85dd4a7cc1668ebb2ad263b00d694 --- /dev/null +++ b/x_track/src/App/Utils/MapView/MapView.cpp @@ -0,0 +1,449 @@ +/* + * MIT License + * Copyright (c) 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "MapView.h" +#include "Utils/lv_msg/lv_msg.h" +#include <string.h> + +#define MAP_MOVED_STATE LV_STATE_USER_1 +#define MAP_RESET_TIMEOUT (60 * 1000) + +MapView::MapView( + uint32_t width, + uint32_t height, + lv_obj_t* parent, + EventListener* listener, + void* userData) + : _tileView(width, height) + , _mapConv(true, 16) + , _curGeoCoord { 0 } + , _curLevel(0) + , _config { 0 } + , _viewCont(nullptr) + , _timerMapReset(nullptr) + , _lastShortClickTime(0) + , _animBusy(false) + , _animStart { 0 } + , _animEnd { 0 } + , _tileSrcEanble(true) + , _listener(listener) + , _userData(userData) +{ + _tileView.setFocusPos(0, 0); + + lv_obj_t* viewCont = lv_obj_create(parent); + _viewCont = viewCont; + lv_obj_remove_style_all(viewCont); + lv_obj_set_size(viewCont, width, height); + + /* scoll auto load map view */ + lv_obj_add_event( + viewCont, [](lv_event_t* e) { + if (!lv_indev_get_act()) { + return; + } + + auto self = (MapView*)lv_event_get_user_data(e); + self->onMoving(); + + /* update focus point */ + auto focus = self->getFocus(); + self->setFocusRow(focus.x, focus.y); + }, + LV_EVENT_SCROLL, this); + + /* double click to zoom up */ + lv_obj_add_event( + viewCont, [](lv_event_t* e) { + auto self = (MapView*)lv_event_get_user_data(e); + + /* not scrollable */ + if (!lv_obj_has_flag(self->_viewCont, LV_OBJ_FLAG_SCROLLABLE)) { + return; + } + + /* double click */ + if (lv_tick_elaps(self->_lastShortClickTime) < 300) { + if (self->setLevel(self->_curLevel + 1)) { + self->sendEvent(EVENT_ID::LEVEL_CHANGED, &self->_curLevel); + } + + self->onMoving(); + + lv_point_t p; + lv_indev_get_point(lv_indev_get_act(), &p); + + lv_obj_t* cont = lv_obj_get_child(self->_viewCont, 0); + lv_area_t area; + lv_obj_get_coords(cont, &area); + + int32_t x = p.x - area.x1; + int32_t y = p.y - area.y1; + + auto oriPos = self->_tileView.getTilePos(0); + self->setFocus(oriPos.x + x, oriPos.y + y, LV_ANIM_ON); + } + + self->_lastShortClickTime = lv_tick_get(); + }, + LV_EVENT_SHORT_CLICKED, this); + + /* coord change move map */ + subscribe(MSG_ID::GEO_COORD_UPDATE, viewCont, [](lv_event_t* e) { + auto self = (MapView*)lv_event_get_user_data(e); + auto msg = lv_event_get_msg(e); + + if (self->msgID(MSG_ID::GEO_COORD_UPDATE) != lv_msg_get_id(msg)) { + return; + } + auto obj = lv_event_get_target_obj(e); + + /* not move when edit */ + if (lv_obj_has_state(obj, MAP_MOVED_STATE)) { + return; + } + + auto geoCoord = (const GeoCoord_t*)lv_msg_get_payload(msg); + self->setFocus(geoCoord); + }); + + subscribe(MSG_ID::MOVE_RESET, viewCont, [](lv_event_t* e) { + auto self = (MapView*)lv_event_get_user_data(e); + auto msg = lv_event_get_msg(e); + + if (self->msgID(MSG_ID::MOVE_RESET) != lv_msg_get_id(msg)) { + return; + } + + auto obj = lv_event_get_current_target_obj(e); + lv_obj_clear_state(obj, MAP_MOVED_STATE); + self->setFocus(&self->_curGeoCoord, true); + }); + + /* map cont */ + { + lv_obj_t* cont = lv_obj_create(viewCont); + lv_obj_remove_style_all(cont); + lv_obj_clear_flag(cont, LV_OBJ_FLAG_CLICKABLE); + + TileView::Rect_t rect = _tileView.getTileRect(); + lv_obj_set_size(cont, rect.width, rect.height); + + /* map img */ + uint32_t tileNum = _tileView.getTileNum(); + for (uint32_t i = 0; i < tileNum; i++) { + lv_obj_t* img = lv_img_create(cont); + lv_obj_remove_style_all(img); + } + } + + /* map reset timer */ + _timerMapReset = lv_timer_create([](lv_timer_t* timer) { + auto self = (MapView*)lv_timer_get_user_data(timer); + self->publish(MSG_ID::MOVE_RESET); + self->sendEvent(EVENT_ID::MOVE_RESET); + lv_timer_pause(timer); + }, + 0, this); + lv_timer_pause(_timerMapReset); +} + +MapView::~MapView() +{ + lv_timer_del(_timerMapReset); + lv_obj_del(_viewCont); + _viewCont = nullptr; +} + +void MapView::setConfig(const MapConfig_t* config) +{ + _config = *config; + _mapConv.setCoordTransformEnable(_config.coordTrans); + tileTectInvalidate(); +} + +void MapView::setFocus(int32_t x, int32_t y, bool animEn) +{ + if (animEn) { + lv_anim_t a; + lv_anim_init(&a); + lv_anim_set_var(&a, this); + lv_anim_set_path_cb(&a, lv_anim_path_ease_out); + lv_anim_set_time(&a, 300); + lv_anim_set_values(&a, 0, 1000); + lv_anim_set_ready_cb(&a, [](lv_anim_t* anim) { + auto self = (MapView*)anim->var; + self->cancelAnim(); + }); + + lv_anim_set_exec_cb(&a, [](void* var, int32_t v) { + auto self = (MapView*)var; + int32_t fx = lv_map(v, 0, 1000, self->_animStart.x, self->_animEnd.x); + int32_t fy = lv_map(v, 0, 1000, self->_animStart.y, self->_animEnd.y); + self->setFocusRow(fx, fy); + }); + + _animStart = getFocus(); + _animEnd.x = x; + _animEnd.y = y; + _animBusy = true; + lv_anim_start(&a); + return; + } + + if (_animBusy) { + return; + } + + setFocusRow(x, y); +} + +void MapView::setFocus(const GeoCoord_t* geoCoord, bool animEn) +{ + MapConv::Point_t point = _mapConv.getCoordinate(geoCoord->longitude, geoCoord->latitude); + setFocus(point.x, point.y, animEn); +} + +bool MapView::setLevel(int level) +{ + /* check level */ + if (level < _config.levelMin || level > _config.levelMax) { + return false; + } + + TileView::Point_t cur = _tileView.getFocusPos(); + + _mapConv.setLevel(level); + MapConv::Point_t newPoint = _mapConv.convertPoint(cur.x, cur.y, _curLevel); + _curLevel = level; + tileTectInvalidate(); + cancelAnim(); + setFocus(newPoint.x, newPoint.y); + + return true; +} + +void MapView::setScrollEanble(bool enable) +{ + enable ? lv_obj_add_flag(_viewCont, LV_OBJ_FLAG_SCROLLABLE) : lv_obj_clear_flag(_viewCont, LV_OBJ_FLAG_SCROLLABLE); +} + +void MapView::setTileSrcEanble(bool enable) +{ + _tileSrcEanble = enable; +} + +MapView::MapPoint_t MapView::getFocus() +{ + auto scroll_x = lv_obj_get_scroll_x(_viewCont); + auto scroll_y = lv_obj_get_scroll_y(_viewCont); + auto tilePoint = _tileView.getTilePos(0); + auto focusX = tilePoint.x + scroll_x + _tileView.getViewWidth() / 2; + auto focusY = tilePoint.y + scroll_y + _tileView.getViewHeight() / 2; + return { focusX, focusY }; +} + +lv_obj_t* MapView::getViewCont() +{ + return _viewCont; +} + +MapView::MapPoint_t MapView::getOffset(int32_t x, int32_t y) +{ + TileView::Point_t tilePoint = { x, y }; + auto offset = _tileView.getOffset(&tilePoint); + return { offset.x, offset.y }; +} + +void MapView::addArrowImage(const void* src) +{ + lv_obj_t* img = lv_img_create(_viewCont); + lv_img_set_src(img, src); + lv_obj_add_flag(img, LV_OBJ_FLAG_HIDDEN); + lv_obj_update_layout(img); + lv_obj_set_style_translate_x(img, -lv_obj_get_width(img) / 2, 0); + lv_obj_set_style_translate_y(img, -lv_obj_get_height(img) / 2, 0); + + subscribe(MSG_ID::GEO_COORD_UPDATE, img, [](lv_event_t* e) { + auto obj = lv_event_get_target_obj(e); + auto msg = lv_event_get_msg(e); + auto self = (MapView*)lv_event_get_user_data(e); + + if (self->msgID(MSG_ID::GEO_COORD_UPDATE) != lv_msg_get_id(msg)) { + return; + } + + /* copy geo coord */ + auto geoCoord = (const GeoCoord_t*)lv_msg_get_payload(msg); + + if (!self->arrowCheck(obj)) { + return; + } + + lv_img_set_angle(obj, geoCoord->course * 10); + }); + + subscribe(MSG_ID::TILE_RECT_CHANGED, img, [](lv_event_t* e) { + auto obj = lv_event_get_target_obj(e); + auto msg = lv_event_get_msg(e); + auto self = (MapView*)lv_event_get_user_data(e); + + if (self->msgID(MSG_ID::TILE_RECT_CHANGED) != lv_msg_get_id(msg)) { + return; + } + + self->arrowCheck(obj); + }); +} + +void MapView::setGeoCoord(const GeoCoord_t* geoCoord) +{ + _curGeoCoord = *geoCoord; + publish(MSG_ID::GEO_COORD_UPDATE, &_curGeoCoord); +} + +void MapView::reset(uint32_t timeout) +{ + lv_timer_set_period(_timerMapReset, timeout); + lv_timer_reset(_timerMapReset); + lv_timer_resume(_timerMapReset); +} + +lv_uintptr_t MapView::msgID(MSG_ID id) +{ + return (lv_uintptr_t)this + (lv_uintptr_t)id; +} + +void MapView::publish(MSG_ID id, const void* payload) +{ + lv_msg_send(msgID(id), payload); +} + +void MapView::subscribe(MSG_ID id, lv_obj_t* obj, lv_event_cb_t event_cb) +{ + lv_msg_subscribe_obj(msgID(id), obj, this); + lv_obj_add_event(obj, event_cb, LV_EVENT_MSG_RECEIVED, this); +} + +void MapView::sendEvent(EVENT_ID id, const void* param) +{ + _listener(id, param, _userData); +} + +void MapView::onMoving() +{ + cancelAnim(); + reset(MAP_RESET_TIMEOUT); + + if (lv_obj_has_state(_viewCont, MAP_MOVED_STATE)) { + return; + } + + lv_obj_add_state(_viewCont, MAP_MOVED_STATE); + publish(MSG_ID::MOVE_START); + sendEvent(EVENT_ID::MOVE_START); +} + +void MapView::setFocusRow(int32_t x, int32_t y) +{ + _tileView.setFocusPos(x, y); + TileView::Point offset = _tileView.getTileRectOffset(); + lv_obj_scroll_to(_viewCont, offset.x, offset.y, LV_ANIM_OFF); + + /* map reload check */ + if (!isTileRectChanged()) { + return; + } + + /* tile src */ + if (_tileSrcEanble) { + lv_obj_t* cont = lv_obj_get_child(_viewCont, 0); + uint32_t tileNum = _tileView.getTileNum(); + lv_disp_t* disp = lv_obj_get_disp(cont); + lv_disp_enable_invalidation(disp, false); + for (uint32_t i = 0; i < tileNum; i++) { + TileView::Point_t pos = _tileView.getTilePos(i); + lv_obj_t* img = lv_obj_get_child(cont, i); + + char mapPath[32]; + _mapConv.getPath(mapPath, sizeof(mapPath), pos.x, pos.y); + + char realPath[64]; + lv_snprintf(realPath, sizeof(realPath), + "%s%s.%s", _config.path, mapPath, _config.ext); + + auto imgPos = _tileView.getOffset(&pos); + lv_obj_set_pos(img, imgPos.x, imgPos.y); + lv_img_set_src(img, realPath); + } + lv_disp_enable_invalidation(disp, true); + + /* merge invalid area */ + lv_obj_invalidate(_viewCont); + } + + /* update tile view rect */ + _curTileRect = _tileView.getTileRect(); + LV_LOG_INFO("tile rect: %" LV_PRId32 ", %" LV_PRId32 ", %" LV_PRId32 ", %" LV_PRId32, + _curTileRect.x, _curTileRect.y, _curTileRect.width, _curTileRect.height); + + publish(MSG_ID::TILE_RECT_CHANGED, &_curTileRect); + sendEvent(EVENT_ID::TILE_RECT_CHANGED, &_curTileRect); +} + +void MapView::cancelAnim() +{ + if (!_animBusy) { + return; + } + + lv_anim_del(this, NULL); + _animBusy = false; +} + +bool MapView::isTileRectChanged() +{ + TileView::Rect_t rect = _tileView.getTileRect(); + return memcmp(&_curTileRect, &rect, sizeof(TileView::Rect_t)) != 0; +} + +void MapView::tileTectInvalidate() +{ + lv_memzero(&_curTileRect, sizeof(TileView::Rect_t)); +} + +bool MapView::arrowCheck(lv_obj_t* img) +{ + auto mapCoord = _mapConv.getCoordinate(_curGeoCoord.longitude, _curGeoCoord.latitude); + auto imgPos = _tileView.getOffset((TileView::Point_t*)&mapCoord); + + if (imgPos.x < 0 || imgPos.y < 0 + || imgPos.x > _curTileRect.width || imgPos.y > _curTileRect.height) { + lv_obj_add_flag(img, LV_OBJ_FLAG_HIDDEN); + return false; + } + + lv_obj_set_pos(img, imgPos.x, imgPos.y); + lv_obj_clear_flag(img, LV_OBJ_FLAG_HIDDEN); + return true; +} diff --git a/x_track/src/App/Utils/MapView/MapView.h b/x_track/src/App/Utils/MapView/MapView.h new file mode 100644 index 0000000000000000000000000000000000000000..235631cd7eb04a5dabc191336361ba1c056aa07b --- /dev/null +++ b/x_track/src/App/Utils/MapView/MapView.h @@ -0,0 +1,131 @@ +/* + * MIT License + * Copyright (c) 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __MAP_VIEW_H +#define __MAP_VIEW_H + +#include "Utils/MapConv/MapConv.h" +#include "Utils/TileView/TileView.h" +#include "lvgl/lvgl.h" + +class MapView { +public: + typedef struct + { + float longitude; + float latitude; + float altitude; + float course; + } GeoCoord_t; + + typedef struct + { + const char* path; + const char* ext; + uint8_t levelMin; + uint8_t levelMax; + bool coordTrans; + } MapConfig_t; + + typedef struct + { + int32_t x; + int32_t y; + } MapPoint_t; + + enum class EVENT_ID { + MOVE_START, /* param: None */ + MOVE_RESET, /* param: None */ + LEVEL_CHANGED, /* param: int */ + TILE_RECT_CHANGED, /* param: TileView::Rect_t */ + _LAST, + }; + + typedef void EventListener(EVENT_ID id, const void* param, void* userData); + +public: + MapView(uint32_t width, uint32_t height, lv_obj_t* parent, EventListener* listener, void* userData); + ~MapView(); + + void setConfig(const MapConfig_t* config); + void setFocus(int32_t x, int32_t y, bool animEn = false); + void setFocus(const GeoCoord_t* geoCoord, bool animEn = false); + bool setLevel(int level); + void setScrollEanble(bool enable); + void setTileSrcEanble(bool enable); + MapPoint_t getFocus(); + lv_obj_t* getViewCont(); + MapPoint_t getOffset(int32_t x, int32_t y); + void addArrowImage(const void* src); + void setGeoCoord(const GeoCoord_t* geoCoord); + void reset(uint32_t timeout); + uint32_t getTileSize() { return _tileView.getTileSize(); } + bool getCoordTrans() { return _mapConv.getCoordTransformEnable(); } + +private: + enum class MSG_ID { + GEO_COORD_UPDATE, /* param: GeoCoord_t */ + TILE_RECT_CHANGED, /* param: TileView::Rect_t */ + MOVE_START, /* param: None */ + MOVE_RESET, /* param: None */ + _LAST, + }; + +private: + TileView _tileView; + MapConv _mapConv; + + /* current state */ + TileView::Rect_t _curTileRect; + GeoCoord_t _curGeoCoord; + int _curLevel; + + /* config */ + MapConfig_t _config; + + /* view interaction */ + lv_obj_t* _viewCont; + lv_timer_t* _timerMapReset; + uint32_t _lastShortClickTime; + bool _animBusy; + MapPoint_t _animStart; + MapPoint_t _animEnd; + bool _tileSrcEanble; + + /* event */ + EventListener* _listener; + void* _userData; + +private: + lv_uintptr_t msgID(MSG_ID id); + void publish(MSG_ID id, const void* payload = nullptr); + void subscribe(MSG_ID id, lv_obj_t* obj, lv_event_cb_t event_cb); + void sendEvent(EVENT_ID id, const void* param = nullptr); + void onMoving(); + void setFocusRow(int32_t x, int32_t y); + void cancelAnim(); + bool isTileRectChanged(); + void tileTectInvalidate(); + bool arrowCheck(lv_obj_t* img); +}; + +#endif // __MAP_VIEW_H diff --git a/x_track/src/App/Utils/PointContainer/PointContainer.cpp b/x_track/src/App/Utils/PointContainer/PointContainer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d2bf8ede77971379ffc39241ca7309b9013b8002 --- /dev/null +++ b/x_track/src/App/Utils/PointContainer/PointContainer.cpp @@ -0,0 +1,133 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "PointContainer.h" +#include <limits.h> +#include <stdlib.h> +#include <string.h> + +PointContainer::PointContainer() + : _curPushPoint { 0 } + , _curPopPoint { 0 } + , _curPopIndex(0) +{ +} + +PointContainer::~PointContainer() +{ +} + +bool PointContainer::isFlag(const DiffPoint_t* point) +{ + if (point->x == FLAG_END_POINT && point->y == FLAG_END_POINT) { + return true; + } + + if (point->x == FLAG_FULL_POINT && point->y == FLAG_FULL_POINT) { + return true; + } + + return false; +} + +void PointContainer::pushPoint(const FullPoint_t* point) +{ + size_t curIndex = vecPoints.size(); + if (curIndex == 0) { + _curPushPoint = *point; + pushFullPoint(point); + return; + } + + int32_t diffX = point->x - _curPushPoint.x; + int32_t diffY = point->y - _curPushPoint.y; + _curPushPoint = *point; + + if (std::abs((int)diffX) > SCHAR_MAX || std::abs((int)diffY) > SCHAR_MAX) { + pushFullPoint(point); + return; + } + + const DiffPoint_t diffPoint = { (int8_t)diffX, (int8_t)diffY }; + if (isFlag(&diffPoint)) { + pushFullPoint(point); + return; + } + + vecPoints.push_back(diffPoint); +} + +void PointContainer::pushFullPoint(const FullPoint_t* point) +{ + vecPoints.push_back(makeFlag(FLAG_FULL_POINT)); + const DiffPoint_t* pData = (const DiffPoint_t*)point; + for (size_t i = 0; i < sizeof(FullPoint_t) / sizeof(DiffPoint_t); i++) { + vecPoints.push_back(pData[i]); + } +} + +bool PointContainer::popFullPoint(FullPoint_t* point) +{ + size_t size = vecPoints.size(); + if (size - _curPopIndex < 4) { + return false; + } + + DiffPoint_t* pData = (DiffPoint_t*)point; + for (size_t i = 0; i < sizeof(FullPoint_t) / sizeof(DiffPoint_t); i++) { + pData[i] = vecPoints[_curPopIndex]; + _curPopIndex++; + } + return true; +} + +bool PointContainer::popPoint(FullPoint_t* point) +{ + size_t size = vecPoints.size(); + + if (size - _curPopIndex == 0) { + return false; + } + + DiffPoint_t diffPoint = vecPoints[_curPopIndex]; + + if (diffPoint.x == FLAG_FULL_POINT && diffPoint.y == FLAG_FULL_POINT) { + _curPopIndex++; + if (popFullPoint(point)) { + _curPopPoint = *point; + } else { + return false; + } + } else { + _curPopPoint.x += diffPoint.x; + _curPopPoint.y += diffPoint.y; + *point = _curPopPoint; + _curPopIndex++; + } + + return true; +} + +void PointContainer::popStart() +{ + _curPopIndex = 0; +} diff --git a/x_track/src/App/Utils/PointContainer/PointContainer.h b/x_track/src/App/Utils/PointContainer/PointContainer.h new file mode 100644 index 0000000000000000000000000000000000000000..01331743d13341f9e7ce184f0c4f5ea19ba190d5 --- /dev/null +++ b/x_track/src/App/Utils/PointContainer/PointContainer.h @@ -0,0 +1,90 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __POINT_CONTAINER_H +#define __POINT_CONTAINER_H + +#include <stdint.h> +#include <vector> + +class PointContainer { +public: + PointContainer(); + ~PointContainer(); + void pushPoint(int32_t x, int32_t y) + { + const FullPoint_t point = { x, y }; + pushPoint(&point); + } + + bool popPoint(int32_t* x, int32_t* y) + { + FullPoint_t point; + bool retval = popPoint(&point); + + if (retval) { + *x = point.x; + *y = point.y; + } + return retval; + } + + void popStart(); + +private: + typedef enum { + FLAG_END_POINT = 0, + FLAG_FULL_POINT = 1, + } Flag_t; + + typedef struct + { + int32_t x; + int32_t y; + } FullPoint_t; + + typedef struct + { + int8_t x; + int8_t y; + } DiffPoint_t; + +private: + void pushPoint(const FullPoint_t* point); + bool popPoint(FullPoint_t* point); + void pushFullPoint(const FullPoint_t* point); + bool popFullPoint(FullPoint_t* point); + bool isFlag(const DiffPoint_t* point); + inline DiffPoint_t makeFlag(Flag_t flag) + { + DiffPoint_t point = { (int8_t)flag, (int8_t)flag }; + return point; + } + +private: + std::vector<DiffPoint_t> vecPoints; + FullPoint_t _curPushPoint; + FullPoint_t _curPopPoint; + uint32_t _curPopIndex; +}; + +#endif diff --git a/x_track/src/App/Utils/StorageService/StorageService.cpp b/x_track/src/App/Utils/StorageService/StorageService.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9515aaea64673f24251f1d510a0b8669fbb77660 --- /dev/null +++ b/x_track/src/App/Utils/StorageService/StorageService.cpp @@ -0,0 +1,506 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "StorageService.h" +#include "lvgl/lvgl.h" +#include <algorithm> +#include <cJSON.h> +#include <cstring> +#include <stack> + +#define JSON_GET_ITEM(DEST, JSON_TYPE) \ + do { \ + cJSON* it = cJSON_GetObjectItem(curObj, iter.key); \ + if (!it) { \ + LV_LOG_WARN("Can't get " #JSON_TYPE " item: %s", iter.key); \ + retval = false; \ + } \ + DEST = it->value##JSON_TYPE; \ + } while (0) + +#define JSON_ADD_ITEM(TYPE, OBJ_TYPE) \ + do { \ + TYPE value = *(TYPE*)iter.value; \ + cJSON* it = cJSON_Add##OBJ_TYPE##ToObject(curObj, iter.key, value); \ + LV_ASSERT_MSG(it != nullptr, "Add" #OBJ_TYPE "failed"); \ + } while (0) + +#define JSON_GET_ARRAY(DEST_TYPE, JSON_TYPE) \ + do { \ + for (int i = 0; i < arrSize; i++) { \ + cJSON* it = cJSON_GetArrayItem(json, i); \ + if (!it) { \ + LV_LOG_WARN("Can't get array item: %d", i); \ + return false; \ + } \ + *(TYPE*)value = (TYPE)it->value##JSON_TYPE; \ + value = (TYPE*)value + 1; \ + } \ + } while (0) + +StorageService::StorageService(const char* path) +{ + LV_ASSERT_NULL(path); + _pathDefault = path; + + cJSON_Hooks hooks; + memset(&hooks, 0, sizeof(hooks)); + hooks.malloc_fn = lv_malloc, + hooks.free_fn = lv_free, + cJSON_InitHooks(&hooks); +} + +StorageService::~StorageService() +{ +} + +bool StorageService::add(const char* key, void* value, size_t size, TYPE type) +{ + LV_LOG_INFO("key: %s, value: %p, size: %zu, type: %d", + key, value, size, (int)type); + + switch (type) { + case TYPE::BOOL: + LV_ASSERT(size == sizeof(bool)); + break; + case TYPE::INT: + LV_ASSERT(size == sizeof(int)); + break; + case TYPE::FLOAT: + LV_ASSERT(size == sizeof(float)); + break; + case TYPE::DOUBLE: + LV_ASSERT(size == sizeof(double)); + break; + default: + break; + } + + Node_t node; + node.key = key; + node.value = value; + node.size = size; + node.type = type; + _nodePool.push_back(node); + + return true; +} + +bool StorageService::loadArray(struct cJSON* json, void* value, size_t size, int len, TYPE type) +{ + int arrSize = cJSON_GetArraySize(json); + + if (arrSize != len) { + LV_LOG_WARN("array size(%d) not match: %d", arrSize, len); + } + + len = std::min(arrSize, (int)len); + + if (len <= 0) { + return false; + } + + switch (type) { + case TYPE::BOOL: + JSON_GET_ARRAY(bool, int); + break; + + case TYPE::INT: + JSON_GET_ARRAY(int, int); + break; + + case TYPE::FLOAT: + JSON_GET_ARRAY(float, double); + break; + + case TYPE::DOUBLE: + JSON_GET_ARRAY(double, double); + break; + + case TYPE::STRING: { + for (int i = 0; i < arrSize; i++) { + cJSON* it = cJSON_GetArrayItem(json, i); + if (!it) { + LV_LOG_WARN("Can't get array item: %d", i); + return false; + } + lv_snprintf((char*)value, size, "%s", cJSON_GetStringValue(it)); + value = (char*)value + size; + } + } break; + + default: + LV_LOG_ERROR("Unknow type: %d", (int)type); + return false; + } + + return true; +} + +bool StorageService::saveArray(struct cJSON* json, const char* key, void* value, size_t size, int len, TYPE type) +{ + LV_ASSERT_NULL(json); + LV_ASSERT_NULL(key); + LV_ASSERT_NULL(value); + + bool retval = false; + cJSON* array = nullptr; + + switch (type) { + case TYPE::BOOL: + LV_LOG_WARN("bool array not support"); + break; + + case TYPE::INT: { + array = cJSON_CreateIntArray((const int*)value, len); + retval = cJSON_AddItemToObject(json, key, array); + } break; + + case TYPE::FLOAT: { + array = cJSON_CreateFloatArray((const float*)value, len); + retval = cJSON_AddItemToObject(json, key, array); + } break; + + case TYPE::DOUBLE: { + array = cJSON_CreateDoubleArray((const double*)value, len); + retval = cJSON_AddItemToObject(json, key, array); + } break; + + case TYPE::STRING: { + array = cJSON_CreateStringArray((const char* const*)value, len); + retval = cJSON_AddItemToObject(json, key, array); + } break; + + default: + LV_LOG_ERROR("Unknow type: %d", (int)type); + break; + } + + return retval && array != nullptr; +} + +bool StorageService::load(const char* path) +{ + bool retval = true; + char* buf = nullptr; + cJSON* cjson = nullptr; + cJSON* curObj = nullptr; + cJSON* arrayObj = nullptr; + int arrayLen = 0; + std::stack<cJSON*> objStack; + + path = path ? path : _pathDefault; + + buf = loadJson(path); + if (!buf) { + return false; + } + + cjson = cJSON_Parse(buf); + if (!cjson) { + LV_LOG_ERROR("cJSON_Parse failed"); + retval = false; + goto failed; + } + + curObj = cjson; + + for (auto& iter : _nodePool) { + /* print node info */ + LV_LOG_INFO("key: %s, value: %p, size: %zu, type: %d", + iter.key, iter.value, iter.size, (int)iter.type); + + if (arrayObj) { + retval = loadArray( + arrayObj, + iter.value, + iter.size, + arrayLen, + iter.type); + arrayObj = nullptr; + arrayLen = 0; + continue; + } + + cJSON* item = nullptr; + if (iter.key) { + item = cJSON_GetObjectItem(curObj, iter.key); + if (!item) { + LV_LOG_WARN("could NOT get key: %s", iter.key); + retval = false; + continue; + } + } + + /* skip empty data */ + if (!iter.key && iter.type != TYPE::STRUCT_END) { + continue; + } + + switch (iter.type) { + case TYPE::STRUCT_START: + LV_LOG_INFO("struct start: %s {", iter.key); + objStack.push(curObj); + curObj = cJSON_GetObjectItem(curObj, iter.key); + break; + + case TYPE::STRUCT_END: + if (objStack.empty()) { + LV_LOG_ERROR("struct end not match"); + retval = false; + continue; + } + curObj = objStack.top(); + objStack.pop(); + LV_LOG_INFO("} struct end"); + break; + + case TYPE::ARRAY: + arrayObj = item; + arrayLen = iter.size; + LV_LOG_INFO("array: %s[%d]", iter.key, arrayLen); + break; + + case TYPE::BOOL: + JSON_GET_ITEM(*(bool*)iter.value, int); + break; + + case TYPE::INT: + JSON_GET_ITEM(*(int*)iter.value, int); + break; + + case TYPE::FLOAT: + JSON_GET_ITEM(*(float*)iter.value, double); + break; + + case TYPE::DOUBLE: + JSON_GET_ITEM(*(double*)iter.value, double); + break; + + case TYPE::STRING: { + cJSON* it = cJSON_GetObjectItem(curObj, iter.key); + if (!it) { + LV_LOG_WARN("can't get string item: %s", iter.key); + retval = false; + } + lv_snprintf((char*)iter.value, iter.size, "%s", cJSON_GetStringValue(it)); + } break; + + default: + LV_LOG_ERROR("Unknow type: %d", (int)iter.type); + retval = false; + break; + } + } + +failed: + if (buf) { + lv_free(buf); + } + + if (cjson) { + cJSON_Delete(cjson); + } + + return retval; +} + +bool StorageService::save(const char* path) +{ + bool retval = false; + char* json_str = nullptr; + cJSON* cjson = nullptr; + cJSON* curObj = nullptr; + int arrayLen = 0; + const char* arrayKey = nullptr; + std::stack<cJSON*> objStack; + + cjson = cJSON_CreateObject(); + LV_ASSERT_MALLOC(cjson); + curObj = cjson; + + path = path ? path : _pathDefault; + + if (!cjson) { + LV_LOG_ERROR("can't create cjson object"); + return false; + } + + for (auto& iter : _nodePool) { + /* print node info */ + LV_LOG_INFO("key: %s, value: %p, size: %zu, type: %d", + iter.key, iter.value, iter.size, (int)iter.type); + + if (arrayLen > 0) { + LV_ASSERT(saveArray( + curObj, + arrayKey, + iter.value, + iter.size, + arrayLen, + iter.type)); + arrayLen = 0; + continue; + } + + switch (iter.type) { + case TYPE::STRUCT_START: + LV_LOG_INFO("struct start: %s {", iter.key); + objStack.push(curObj); + curObj = cJSON_AddObjectToObject(curObj, iter.key); + break; + + case TYPE::STRUCT_END: + curObj = objStack.top(); + objStack.pop(); + LV_LOG_INFO("} struct end"); + break; + + case TYPE::ARRAY: + arrayKey = iter.key; + arrayLen = iter.size; + LV_LOG_INFO("array: %s[%d]", iter.key, arrayLen); + break; + + case TYPE::BOOL: + JSON_ADD_ITEM(bool, Bool); + break; + + case TYPE::INT: + JSON_ADD_ITEM(int, Number); + break; + + case TYPE::FLOAT: + JSON_ADD_ITEM(float, Number); + break; + + case TYPE::DOUBLE: + JSON_ADD_ITEM(double, Number); + break; + + case TYPE::STRING: { + cJSON* it = cJSON_AddStringToObject(curObj, iter.key, (const char*)iter.value); + LV_ASSERT_MSG(it != nullptr, "add String failed"); + } break; + + default: + LV_ASSERT_MSG(false, "Unknow type"); + break; + } + } + + json_str = cJSON_Print(cjson); + if (!json_str) { + LV_LOG_ERROR("json_str is NULL"); + goto failed; + } + + retval = saveJson(json_str, path); + +failed: + if (cjson) { + cJSON_Delete(cjson); + } + + if (json_str) { + cJSON_free(json_str); + } + + return retval; +} + +char* StorageService::loadJson(const char* path) +{ + lv_fs_file_t file; + char* buf = NULL; + + /* open json file */ + lv_fs_res_t res = lv_fs_open(&file, path, LV_FS_MODE_RD); + if (res != LV_FS_RES_OK) { + LV_LOG_ERROR("open file: %s faild: %d", path, res); + return NULL; + } + + /* get file size */ + lv_fs_seek(&file, 0, LV_FS_SEEK_END); + uint32_t size; + res = lv_fs_tell(&file, &size); + if (res != LV_FS_RES_OK) { + LV_LOG_ERROR("can't get file size"); + goto failed; + } + lv_fs_seek(&file, 0, LV_FS_SEEK_SET); + + /* alloc string buffer */ + buf = (char*)lv_malloc(size + 1); + LV_ASSERT_MALLOC(buf); + if (!buf) { + LV_LOG_ERROR("malloc failed for json buf"); + goto failed; + } + + /* read json sting */ + uint32_t br; + res = lv_fs_read(&file, buf, size, &br); + lv_fs_close(&file); + if (res != LV_FS_RES_OK || br != size) { + LV_LOG_ERROR("read file failed"); + goto failed; + } + buf[size] = '\0'; + + return buf; + +failed: + lv_fs_close(&file); + + if (buf) { + lv_free(buf); + } + + return NULL; +} + +bool StorageService::saveJson(const char* str, const char* path) +{ + LV_ASSERT_NULL(str); + + uint32_t len = strlen(str); + + lv_fs_file_t file; + lv_fs_res_t res = lv_fs_open(&file, path, LV_FS_MODE_WR); + if (res != LV_FS_RES_OK) { + LV_LOG_ERROR("open file: %s faild: %d", path, res); + return false; + } + + uint32_t bw; + res = lv_fs_write(&file, str, len, &bw); + lv_fs_close(&file); + + if (!(res == LV_FS_RES_OK && bw == len)) { + LV_LOG_ERROR("write file failed: %d", res); + return false; + } + + return true; +} diff --git a/x_track/src/App/Utils/StorageService/StorageService.h b/x_track/src/App/Utils/StorageService/StorageService.h new file mode 100644 index 0000000000000000000000000000000000000000..b0d7d832d5e8925a8780cc91ce0651ac361d3294 --- /dev/null +++ b/x_track/src/App/Utils/StorageService/StorageService.h @@ -0,0 +1,81 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __STORAGE_SERVICE_H +#define __STORAGE_SERVICE_H + +#include <cstddef> +#include <cstdint> +#include <vector> + +struct cJSON; + +class StorageService { +public: + enum class TYPE { + UNKNOW, + BOOL, + INT, + FLOAT, + DOUBLE, + STRING, + ARRAY, + STRUCT_START, + STRUCT_END, + }; + +public: + StorageService(const char* path); + ~StorageService(); + + bool add(const char* key, void* value, size_t size, TYPE type); + bool save(const char* path = nullptr); + bool load(const char* path = nullptr); + +private: + typedef struct Node + { + Node() + : key(nullptr) + , value(nullptr) + , size(0) + , type(TYPE::UNKNOW) + { + } + const char* key; + void* value; + size_t size; + TYPE type; + } Node_t; + +private: + const char* _pathDefault; + std::vector<Node_t> _nodePool; + +private: + char* loadJson(const char* path); + bool saveJson(const char* str, const char* path); + bool loadArray(struct cJSON* json, void* value, size_t size, int len, TYPE type); + bool saveArray(struct cJSON* json, const char* key, void* value, size_t size, int len, TYPE type); +}; + +#endif diff --git a/x_track/src/App/Utils/Stream/Print.cpp b/x_track/src/App/Utils/Stream/Print.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dd0ef02dec07bfe587fb88405e157c117fdcb680 --- /dev/null +++ b/x_track/src/App/Utils/Stream/Print.cpp @@ -0,0 +1,376 @@ +/* + * Print.cpp - Base class that provides print() and println() + * Copyright (c) 2008 David A. Mellis. All right reserved. + * Copyright (c) 2011 LeafLabs, LLC. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * Modified 23 November 2006 by David A. Mellis + * Modified 12 April 2011 by Marti Bolivar <mbolivar@leaflabs.com> + */ + +#include "Print.h" +//#include "wirish_math.h" +#include "limits.h" + +#define ABS(x) (((x)>0)?(x):-(x)) + +#ifndef LLONG_MAX +/* + * Note: + * + * At time of writing (12 April 2011), the limits.h that came with the + * newlib we distributed didn't include LLONG_MAX. Because we're + * staying away from using templates (see /notes/coding_standard.rst, + * "Language Features and Compiler Extensions"), this value was + * copy-pasted from a println() of the value + * + * std::numeric_limits<long long>::max(). + */ +#define LLONG_MAX 9223372036854775807LL +#endif + +/* + * Public methods + */ + +size_t Print::write(const char *str) +{ + size_t n = 0; + while (*str) + { + write(*str++); + n++; + } + return n; +} + +size_t Print::write(const void *buffer, uint32_t size) +{ + size_t n = 0; + uint8_t *ch = (uint8_t*)buffer; + while (size--) + { + write(*ch++); + n++; + } + return n; +} + +size_t Print::print(uint8_t b, int base) +{ + return print((uint64_t)b, base); +} + +size_t Print::print(const String &s) +{ + return write(s.c_str(), s.length()); +} + +size_t Print::print(char c) +{ + return write(c); +} + +size_t Print::print(const char str[]) +{ + return write(str); +} + +size_t Print::print(int n, int base) +{ + return print((long long)n, base); +} + +size_t Print::print(unsigned int n, int base) +{ + return print((unsigned long long)n, base); +} + +size_t Print::print(long n, int base) +{ + return print((long long)n, base); +} + +size_t Print::print(unsigned long n, int base) +{ + return print((unsigned long long)n, base); +} + +size_t Print::print(long long n, int base) +{ + if (base == BYTE) + { + return write((uint8_t)n); + } + if (n < 0) + { + print('-'); + n = -n; + } + return printNumber(n, base); +} + +size_t Print::print(unsigned long long n, int base) +{ + size_t c = 0; + if (base == BYTE) + { + c = write((uint8_t)n); + } + else + { + c = printNumber(n, base); + } + return c; +} + +size_t Print::print(double n, int digits) +{ + return printFloat(n, digits); +} + +size_t Print::print(const __FlashStringHelper *ifsh) +{ + return print(reinterpret_cast<const char *>(ifsh)); +} + +size_t Print::print(const Printable& x) +{ + return x.printTo(*this); +} + +size_t Print::println(void) +{ + size_t n = print('\r'); + n += print('\n'); + return n; +} + +size_t Print::println(const String &s) +{ + size_t n = print(s); + n += println(); + return n; +} + +size_t Print::println(char c) +{ + size_t n = print(c); + n += println(); + return n; +} + +size_t Print::println(const char c[]) +{ + size_t n = print(c); + n += println(); + return n; +} + +size_t Print::println(uint8_t b, int base) +{ + size_t n = print(b, base); + n += println(); + return n; +} + +size_t Print::println(int n, int base) +{ + size_t s = print(n, base); + s += println(); + return s; +} + +size_t Print::println(unsigned int n, int base) +{ + size_t s = print(n, base); + s += println(); + return s; +} + +size_t Print::println(long n, int base) +{ + size_t s = print((long long)n, base); + s += println(); + return s; +} + +size_t Print::println(unsigned long n, int base) +{ + size_t s = print((unsigned long long)n, base); + s += println(); + return s; +} + +size_t Print::println(long long n, int base) +{ + size_t s = print(n, base); + s += println(); + return s; +} + +size_t Print::println(unsigned long long n, int base) +{ + size_t s = print(n, base); + s += println(); + return s; +} + +size_t Print::println(double n, int digits) +{ + size_t s = print(n, digits); + s += println(); + return s; +} + +size_t Print::println(const __FlashStringHelper *ifsh) +{ + size_t n = print(ifsh); + n += println(); + return n; +} + +size_t Print::println(const Printable& x) +{ + size_t n = print(x); + n += println(); + return n; +} + +#ifdef SUPPORTS_PRINTF + +extern "C" { +#include <stdio.h> +#include <stdarg.h> +} + +// Work in progress to support printf. +// Need to implement stream FILE to write individual chars to chosen serial port +int Print::printf (const char *__restrict __format, ...) +{ + char printf_buff[PRINTF_BUFFER_LENGTH]; + + va_list args; + va_start(args, __format); + int ret_status = vsnprintf(printf_buff, sizeof(printf_buff), __format, args); + //int ret_status = vsprintf(printf_buff,__format, args); + va_end(args); + print(printf_buff); + + return ret_status; +} +#endif + +/* + * Private methods + */ + +size_t Print::printNumber(unsigned long long n, uint8_t base) +{ + unsigned char buf[CHAR_BIT * sizeof(long long)]; + unsigned long i = 0; + size_t s = 0; + if (n == 0) + { + print('0'); + return 1; + } + + while (n > 0) + { + buf[i++] = n % base; + n /= base; + } + + for (; i > 0; i--) + { + s += print((char)(buf[i - 1] < 10 ? + '0' + buf[i - 1] : + 'A' + buf[i - 1] - 10)); + } + return s; +} + + +/* According to snprintf(), + * + * nextafter((double)numeric_limits<long long>::max(), 0.0) ~= 9.22337e+18 + * + * This slightly smaller value was picked semi-arbitrarily. */ +#define LARGE_DOUBLE_TRESHOLD (9.1e18) + +/* THIS FUNCTION SHOULDN'T BE USED IF YOU NEED ACCURATE RESULTS. + * + * This implementation is meant to be simple and not occupy too much + * code size. However, printing floating point values accurately is a + * subtle task, best left to a well-tested library function. + * + * See Steele and White 2003 for more details: + * + * http://kurtstephens.com/files/p372-steele.pdf + */ +size_t Print::printFloat(double number, uint8_t digits) +{ + size_t s = 0; + // Hackish fail-fast behavior for large-magnitude doubles + if (ABS(number) >= LARGE_DOUBLE_TRESHOLD) + { + if (number < 0.0) + { + s = print('-'); + } + s += print("<large double>"); + return s; + } + + // Handle negative numbers + if (number < 0.0) + { + s += print('-'); + number = -number; + } + + // Simplistic rounding strategy so that e.g. print(1.999, 2) + // prints as "2.00" + double rounding = 0.5; + for (uint8_t i = 0; i < digits; i++) + { + rounding /= 10.0; + } + number += rounding; + + // Extract the integer part of the number and print it + long long int_part = (long long)number; + double remainder = number - int_part; + s += print(int_part); + + // Print the decimal point, but only if there are digits beyond + if (digits > 0) + { + s += print("."); + } + + // Extract digits from the remainder one at a time + while (digits-- > 0) + { + remainder *= 10.0; + int to_print = (int)remainder; + s += print(to_print); + remainder -= to_print; + } + return s; +} diff --git a/x_track/src/App/Utils/Stream/Print.h b/x_track/src/App/Utils/Stream/Print.h new file mode 100644 index 0000000000000000000000000000000000000000..9f876f32c804deebdbac7f3ced43dad06af3c29e --- /dev/null +++ b/x_track/src/App/Utils/Stream/Print.h @@ -0,0 +1,109 @@ +/* + * Print.h - Base class that provides print() and println() + * Copyright (c) 2008 David A. Mellis. All right reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA. + * + * Modified 12 April 2011 by Marti Bolivar <mbolivar@leaflabs.com> + */ + +#ifndef _PRINT_H_ +#define _PRINT_H_ + +#include <stdint.h> +#include "../WString/WString.h" +#include "Printable.h" + +#define SUPPORTS_PRINTF 1 +#define PRINTF_BUFFER_LENGTH 128 + +enum +{ + BYTE = 0, + BIN = 2, + OCT = 8, + DEC = 10, + HEX = 16 +}; + +class Print +{ +public: + virtual size_t write(uint8_t ch) = 0; + virtual size_t write(const char *str); + virtual size_t write(const void *buf, uint32_t len); + + size_t print(const String &); + size_t print(char); + size_t print(const char[]); + size_t print(uint8_t, int = DEC); + size_t print(int, int = DEC); + size_t print(unsigned int, int = DEC); + size_t print(long, int = DEC); + size_t print(unsigned long, int = DEC); + size_t print(long long, int = DEC); + size_t print(unsigned long long, int = DEC); + size_t print(double, int = 2); + size_t print(const __FlashStringHelper *); + size_t print(const Printable&); + size_t println(void); + size_t println(const String &s); + size_t println(char); + size_t println(const char[]); + size_t println(uint8_t, int = DEC); + size_t println(int, int = DEC); + size_t println(unsigned int, int = DEC); + size_t println(long, int = DEC); + size_t println(unsigned long, int = DEC); + size_t println(long long, int = DEC); + size_t println(unsigned long long, int = DEC); + size_t println(double, int = 2); + size_t println(const __FlashStringHelper *); + size_t println(const Printable&); +#ifdef SUPPORTS_PRINTF +// Roger Clark. Work in progress to add printf support + int printf(const char * format, ...); +#endif + Print() : write_error(0) {} + + int getWriteError() + { + return write_error; + } + void clearWriteError() + { + setWriteError(0); + } + +protected: + void setWriteError(int err = 1) + { + write_error = err; + } + +private: + int write_error; + size_t printNumber(unsigned long long, uint8_t); + size_t printFloat(double, uint8_t); +}; + +template<class T> inline Print &operator << (Print &obj, T arg) +{ + obj.print(arg); + return obj; +} + +#endif diff --git a/x_track/src/App/Utils/Stream/Printable.h b/x_track/src/App/Utils/Stream/Printable.h new file mode 100644 index 0000000000000000000000000000000000000000..3a33e9ac21940876b5509bac4bbd5c16592ef7c7 --- /dev/null +++ b/x_track/src/App/Utils/Stream/Printable.h @@ -0,0 +1,40 @@ +/* + Printable.h - Interface class that allows printing of complex types + Copyright (c) 2011 Adrian McEwen. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef Printable_h +#define Printable_h + +#include <stdlib.h> + +class Print; + +/** The Printable class provides a way for new classes to allow themselves to be printed. + By deriving from Printable and implementing the printTo method, it will then be possible + for users to print out instances of this class by passing them into the usual + Print::print and Print::println methods. +*/ + +class Printable +{ +public: + virtual size_t printTo(Print& p) const = 0; +}; + +#endif + diff --git a/x_track/src/App/Utils/Stream/Stream.cpp b/x_track/src/App/Utils/Stream/Stream.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d7d004f59792a0a6074014cf02eef3d1e39b29f0 --- /dev/null +++ b/x_track/src/App/Utils/Stream/Stream.cpp @@ -0,0 +1,362 @@ +/* + Stream.cpp - adds parsing methods to Stream class + Copyright (c) 2008 David A. Mellis. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Created July 2011 + parsing functions based on TextFinder library by Michael Margolis + */ + +#include "Stream.h" +#include "lvgl/lvgl.h" + +#define millis() lv_tick_get() + +#define PARSE_TIMEOUT 1000 // default number of milli-seconds to wait +#define NO_SKIP_CHAR 1 // a magic char not found in a valid ASCII numeric field + +// private method to read stream with timeout +int Stream::timedRead() +{ + int c; + _startMillis = millis(); + do + { + c = read(); + if (c >= 0) return c; + } + while(millis() - _startMillis < _timeout); + return -1; // -1 indicates timeout +} + +// private method to peek stream with timeout +int Stream::timedPeek() +{ + int c; + _startMillis = millis(); + do + { + c = peek(); + if (c >= 0) return c; + } + while(millis() - _startMillis < _timeout); + return -1; // -1 indicates timeout +} + +// returns peek of the next digit in the stream or -1 if timeout +// discards non-numeric characters +int Stream::peekNextDigit() +{ + int c; + while (1) + { + c = timedPeek(); + if (c < 0) return c; // timeout + if (c == '-') return c; + if (c >= '0' && c <= '9') return c; + read(); // discard non-numeric + } +} + +// Public Methods +////////////////////////////////////////////////////////////// + +void Stream::setTimeout(unsigned long timeout) // sets the maximum number of milliseconds to wait +{ + _timeout = timeout; +} + +// find returns true if the target string is found +bool Stream::find(char *target) +{ + return findUntil(target, (char*)""); +} + +// reads data from the stream until the target string of given length is found +// returns true if target string is found, false if timed out +bool Stream::find(char *target, size_t length) +{ + return findUntil(target, length, NULL, 0); +} + +// as find but search ends if the terminator string is found +bool Stream::findUntil(char *target, char *terminator) +{ + return findUntil(target, strlen(target), terminator, strlen(terminator)); +} + +// reads data from the stream until the target string of the given length is found +// search terminated if the terminator string is found +// returns true if target string is found, false if terminated or timed out +bool Stream::findUntil(char *target, size_t targetLen, char *terminator, size_t termLen) +{ + size_t index = 0; // maximum target string length is 64k bytes! + size_t termIndex = 0; + int c; + + if( *target == 0) + return true; // return true if target is a null string + while( (c = timedRead()) > 0) + { + + if(c != target[index]) + index = 0; // reset index if any char does not match + + if( c == target[index]) + { + //////Serial.print("found "); Serial.write(c); Serial.print("index now"); Serial.println(index+1); + if(++index >= targetLen) // return true if all chars in the target match + { + return true; + } + } + + if(termLen > 0 && c == terminator[termIndex]) + { + if(++termIndex >= termLen) + return false; // return false if terminate string found before target string + } + else + termIndex = 0; + } + return false; +} + + +// returns the first valid (long) integer value from the current position. +// initial characters that are not digits (or the minus sign) are skipped +// function is terminated by the first character that is not a digit. +long Stream::parseInt() +{ + return parseInt(NO_SKIP_CHAR); // terminate on first non-digit character (or timeout) +} + +// as above but a given skipChar is ignored +// this allows format characters (typically commas) in values to be ignored +long Stream::parseInt(char skipChar) +{ + bool isNegative = false; + long value = 0; + int c; + + c = peekNextDigit(); + // ignore non numeric leading characters + if(c < 0) + return 0; // zero returned if timeout + + do + { + if(c == skipChar) + ; // ignore this charactor + else if(c == '-') + isNegative = true; + else if(c >= '0' && c <= '9') // is c a digit? + value = value * 10 + c - '0'; + read(); // consume the character we got with peek + c = timedPeek(); + } + while( (c >= '0' && c <= '9') || c == skipChar ); + + if(isNegative) + value = -value; + return value; +} + + +// as parseInt but returns a floating point value +float Stream::parseFloat() +{ + return parseFloat(NO_SKIP_CHAR); +} + +// as above but the given skipChar is ignored +// this allows format characters (typically commas) in values to be ignored +float Stream::parseFloat(char skipChar) +{ + bool isNegative = false; + bool isFraction = false; + long value = 0; + int c; + float fraction = 1.0f; + + c = peekNextDigit(); + // ignore non numeric leading characters + if(c < 0) + return 0; // zero returned if timeout + + do + { + if(c == skipChar) + ; // ignore + else if(c == '-') + isNegative = true; + else if (c == '.') + isFraction = true; + else if(c >= '0' && c <= '9') // is c a digit? + { + value = value * 10 + c - '0'; + if(isFraction) + fraction *= 0.1f; + } + read(); // consume the character we got with peek + c = timedPeek(); + } + while( (c >= '0' && c <= '9') || c == '.' || c == skipChar ); + + if(isNegative) + value = -value; + if(isFraction) + return value * fraction; + else + return (float)value; +} + +// read characters from stream into buffer +// terminates if length characters have been read, or timeout (see setTimeout) +// returns the number of characters placed in the buffer +// the buffer is NOT null terminated. +// +size_t Stream::readBytes(char *buffer, size_t length) +{ + size_t count = 0; + while (count < length) + { + int c = timedRead(); + if (c < 0) break; + *buffer++ = (char)c; + count++; + } + return count; +} + + +// as readBytes with terminator character +// terminates if length characters have been read, timeout, or if the terminator character detected +// returns the number of characters placed in the buffer (0 means no valid data found) + +size_t Stream::readBytesUntil(char terminator, char *buffer, size_t length) +{ + if (length < 1) return 0; + size_t index = 0; + while (index < length) + { + int c = timedRead(); + if (c < 0 || c == terminator) break; + *buffer++ = (char)c; + index++; + } + return index; // return number of characters, not including null terminator +} + +String Stream::readString() +{ + String ret; + int c = timedRead(); + while (c >= 0) + { + ret += (char)c; + c = timedRead(); + } + return ret; +} + +String Stream::readStringUntil(char terminator) +{ + String ret; + int c = timedRead(); + while (c >= 0 && c != terminator) + { + ret += (char)c; + c = timedRead(); + } + return ret; +} + + +int Stream::findMulti( struct Stream::MultiTarget *targets, int tCount) +{ + // any zero length target string automatically matches and would make + // a mess of the rest of the algorithm. + for (struct MultiTarget *t = targets; t < targets + tCount; ++t) + { + if (t->len <= 0) + return t - targets; + } + + while (1) + { + int c = timedRead(); + if (c < 0) + return -1; + + for (struct MultiTarget *t = targets; t < targets + tCount; ++t) + { + // the simple case is if we match, deal with that first. + if (c == t->str[t->index]) + { + if (++t->index == t->len) + return t - targets; + else + continue; + } + + // if not we need to walk back and see if we could have matched further + // down the stream (ie '1112' doesn't match the first position in '11112' + // but it will match the second position so we can't just reset the current + // index to 0 when we find a mismatch. + if (t->index == 0) + continue; + + int origIndex = t->index; + do + { + --t->index; + // first check if current char works against the new current index + if (c != t->str[t->index]) + continue; + + // if it's the only char then we're good, nothing more to check + if (t->index == 0) + { + t->index++; + break; + } + + // otherwise we need to check the rest of the found string + int diff = origIndex - t->index; + size_t i; + for (i = 0; i < t->index; ++i) + { + if (t->str[i] != t->str[i + diff]) + break; + } + + // if we successfully got through the previous loop then our current + // index is good. + if (i == t->index) + { + t->index++; + break; + } + + // otherwise we just try the next index + } + while (t->index); + } + } + // unreachable +// return -1; +} diff --git a/x_track/src/App/Utils/Stream/Stream.h b/x_track/src/App/Utils/Stream/Stream.h new file mode 100644 index 0000000000000000000000000000000000000000..5d93255aa0f810851cd31b0ce8d8a1c581402e3f --- /dev/null +++ b/x_track/src/App/Utils/Stream/Stream.h @@ -0,0 +1,134 @@ +/* + Stream.h - base class for character-based streams. + Copyright (c) 2010 David A. Mellis. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + parsing functions based on TextFinder library by Michael Margolis +*/ + +#ifndef Stream_h +#define Stream_h + +#include <inttypes.h> +#include "Print.h" + +// compatability macros for testing +/* +#define getInt() parseInt() +#define getInt(skipChar) parseInt(skipchar) +#define getFloat() parseFloat() +#define getFloat(skipChar) parseFloat(skipChar) +#define getString( pre_string, post_string, buffer, length) +readBytesBetween( pre_string, terminator, buffer, length) +*/ + +class Stream : public Print +{ +protected: + unsigned long _timeout; // number of milliseconds to wait for the next char before aborting timed read + unsigned long _startMillis; // used for timeout measurement + int timedRead(); // private method to read stream with timeout + int timedPeek(); // private method to peek stream with timeout + int peekNextDigit(); // returns the next numeric digit in the stream or -1 if timeout + +public: + virtual int available() = 0; + virtual int read() = 0; + virtual int peek() = 0; + virtual void flush() = 0; + + Stream() { + _timeout = 1000; + _startMillis = 0; + } + +// parsing methods + + void setTimeout(unsigned long timeout); // sets maximum milliseconds to wait for stream data, default is 1 second + unsigned long getTimeout(void) { + return _timeout; + } + + bool find(char *target); // reads data from the stream until the target string is found + bool find(uint8_t *target) { + return find ((char *)target); + } + // returns true if target string is found, false if timed out (see setTimeout) + + bool find(char *target, size_t length); // reads data from the stream until the target string of given length is found + bool find(uint8_t *target, size_t length) { + return find ((char *)target, length); + } + // returns true if target string is found, false if timed out + + bool find(char target) { + return find (&target, 1); + } + + bool findUntil(char *target, char *terminator); // as find but search ends if the terminator string is found + bool findUntil(uint8_t *target, char *terminator) { + return findUntil((char *)target, terminator); + } + + bool findUntil(char *target, size_t targetLen, char *terminate, size_t termLen); // as above but search ends if the terminate string is found + bool findUntil(uint8_t *target, size_t targetLen, char *terminate, size_t termLen) { + return findUntil((char *)target, targetLen, terminate, termLen); + } + + + long parseInt(); // returns the first valid (long) integer value from the current position. + // initial characters that are not digits (or the minus sign) are skipped + // integer is terminated by the first character that is not a digit. + + float parseFloat(); // float version of parseInt + + size_t readBytes( char *buffer, size_t length); // read chars from stream into buffer + size_t readBytes( uint8_t *buffer, size_t length) { + return readBytes((char *)buffer, length); + } + // terminates if length characters have been read or timeout (see setTimeout) + // returns the number of characters placed in the buffer (0 means no valid data found) + + size_t readBytesUntil( char terminator, char *buffer, size_t length); // as readBytes with terminator character + size_t readBytesUntil( char terminator, uint8_t *buffer, size_t length) { + return readBytesUntil(terminator, (char *)buffer, length); + } + // terminates if length characters have been read, timeout, or if the terminator character detected + // returns the number of characters placed in the buffer (0 means no valid data found) + + // Arduino String functions to be added here + String readString(); + String readStringUntil(char terminator); + +protected: + long parseInt(char skipChar); // as above but the given skipChar is ignored + // as above but the given skipChar is ignored + // this allows format characters (typically commas) in values to be ignored + + float parseFloat(char skipChar); // as above but the given skipChar is ignored + + struct MultiTarget { + const char *str; // string you're searching for + size_t len; // length of string you're searching for + size_t index; // index used by the search routine. + }; + + // This allows you to search for an arbitrary number of strings. + // Returns index of the target that is found first or -1 if timeout occurs. + int findMulti(struct MultiTarget *targets, int tCount); +}; + +#endif diff --git a/x_track/src/App/Utils/SunRiseCalc/SunRiseCalc.c b/x_track/src/App/Utils/SunRiseCalc/SunRiseCalc.c new file mode 100644 index 0000000000000000000000000000000000000000..958bd08f8065af83ea5d4138b040069d31848015 --- /dev/null +++ b/x_track/src/App/Utils/SunRiseCalc/SunRiseCalc.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 HanfG + */ +#include "SunRiseCalc.h" +#include <math.h> +#include <stdbool.h> + +#define PI 3.1415926f +#define ACOS acosf +#define COS cosf +#define TAN tanf + +static bool isLeapYear(uint16_t year) +{ + if (year % 4 != 0) + return false; + if (year % 100 != 0) + return true; + if (year % 400 == 0) + return true; + return false; +} + +static uint16_t dayOfYear(uint16_t year, uint8_t month, uint8_t day) +{ + uint16_t days = 0; + if (month > 2 && isLeapYear(year)) { + days += 1; + } + for (uint8_t i = 0; i < month; i++) { + static const uint8_t daysOfMonth[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30 }; + days += daysOfMonth[i]; + } + return days; +} + +void SunRiseSunSetCalculator( + int8_t timeZone, + uint16_t year, + uint8_t month, + uint8_t day, + float longitude, + float latitude, + uint8_t* sunriseHour, + uint8_t* sunriseMinute, + uint8_t* sunsetHour, + uint8_t* sunsetMinute) +{ + float sunriseFloat = 24 * (180 + timeZone * 15 - longitude - ACOS(-TAN(-23.4f * COS(2 * PI * (dayOfYear(year, month, day) + 9) / 365) * PI / 180) * TAN(latitude * PI / 180)) * 180 / PI) / 360; + float sunsetFloat = 24 * (1 + (timeZone * 15 - longitude) / 180) - sunriseFloat; + + *sunriseHour = (uint8_t)sunriseFloat; + *sunriseMinute = (uint8_t)((sunriseFloat - *sunriseHour) * 60); + *sunsetHour = (uint8_t)sunsetFloat; + *sunsetMinute = (uint8_t)((sunsetFloat - *sunsetHour) * 60); +} diff --git a/x_track/src/App/Utils/SunRiseCalc/SunRiseCalc.h b/x_track/src/App/Utils/SunRiseCalc/SunRiseCalc.h new file mode 100644 index 0000000000000000000000000000000000000000..5725fb174cb336f033871ba60a52f1ff4e1db8be --- /dev/null +++ b/x_track/src/App/Utils/SunRiseCalc/SunRiseCalc.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 HanfG + */ +#ifndef __SUN_RISE_CALC_H__ +#define __SUN_RISE_CALC_H__ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Calculate the sunrise and sunset time + * @param timeZone Time zone + * @param year Year + * @param month Month + * @param day Day + * @param longitude Longitude + * @param latitude Latitude + * @param sunriseHour Sunrise hour + * @param sunriseMinute Sunrise minute + * @param sunsetHour Sunset hour + * @param sunsetMinute Sunset minute + */ +void SunRiseSunSetCalculator( + int8_t timeZone, + uint16_t year, + uint8_t month, + uint8_t day, + float longitude, + float latitude, + uint8_t* sunriseHour, + uint8_t* sunriseMinute, + uint8_t* sunsetHour, + uint8_t* sunsetMinute); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __SUN_RISE_CALC_H__ */ diff --git a/x_track/src/App/Utils/TileView/TileView.cpp b/x_track/src/App/Utils/TileView/TileView.cpp new file mode 100644 index 0000000000000000000000000000000000000000..493ff5331ad26b7f3686737c2ac1e875aaf952a5 --- /dev/null +++ b/x_track/src/App/Utils/TileView/TileView.cpp @@ -0,0 +1,144 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "TileView.h" +#include <string.h> + +TileView::TileView(uint32_t viewWidth, uint32_t viewHeight, uint32_t tileSize) + : _viewWidth(viewWidth) + , _viewHeight(viewHeight) + , _tileSize(tileSize) +{ +} + +void TileView::setViewSize(uint32_t width, uint32_t height) +{ + _viewWidth = width; + _viewHeight = height; +} + +void TileView::setFocusPos(int32_t x, int32_t y) +{ + _pointFocus.x = x; + _pointFocus.y = y; + + const int32_t viewHalfWidth = _viewWidth / 2; + const int32_t viewHalfHeight = _viewHeight / 2; + const uint32_t tileSize = _tileSize; + + _pointView[0].x = x - viewHalfWidth; + _pointView[0].y = y - viewHalfHeight; + _pointView[1].x = x + viewHalfWidth; + _pointView[1].y = y - viewHalfHeight; + _pointView[2].x = x - viewHalfWidth; + _pointView[2].y = y + viewHalfHeight; + _pointView[3].x = x + viewHalfWidth; + _pointView[3].y = y + viewHalfHeight; + + const int32_t tileContWidth = (_viewWidth / tileSize + 2) * tileSize; + const int32_t tileContHeight = (_viewHeight / tileSize + 2) * tileSize; + + _pointTileCont[0].x = roundTile(_pointView[0].x, false); + _pointTileCont[0].y = roundTile(_pointView[0].y, false); + _pointTileCont[1].x = _pointTileCont[0].x + tileContWidth; + _pointTileCont[1].y = _pointTileCont[0].y; + _pointTileCont[2].x = _pointTileCont[0].x; + _pointTileCont[2].y = _pointTileCont[0].y + tileContHeight; + _pointTileCont[3].x = _pointTileCont[0].x + tileContWidth; + _pointTileCont[3].y = _pointTileCont[0].y + tileContHeight; +} + +TileView::Point_t TileView::getFocusPos() +{ + return _pointFocus; +} + +TileView::Point_t TileView::getFocusOffset() +{ + return getOffset(&_pointFocus); +} + +void TileView::setTileSize(uint32_t size) +{ + _tileSize = size; +} + +uint32_t TileView::getTileSize() +{ + return _tileSize; +} + +TileView::Rect_t TileView::getTileRect() +{ + Rect_t rect; + rect.x = _pointTileCont[0].x; + rect.y = _pointTileCont[0].y; + rect.width = _pointTileCont[1].x - _pointTileCont[0].x; + rect.height = _pointTileCont[2].y - _pointTileCont[0].y; + return rect; +} + +TileView::Point_t TileView::getTileRectOffset() +{ + return getOffset(&_pointView[0]); +} + +TileView::Point_t TileView::getTilePos(uint32_t index) +{ + Point_t pos; + int32_t width = _pointTileCont[1].x - _pointTileCont[0].x; + int32_t widthIndexMax = width / _tileSize; + pos.x = _pointTileCont[0].x + _tileSize * (index % widthIndexMax); + pos.y = _pointTileCont[0].y + _tileSize * (index / widthIndexMax); + return pos; +} + +uint32_t TileView::getTileNum() +{ + Rect_t rect = getTileRect(); + return (rect.width / _tileSize) * (rect.height / _tileSize); +} + +int32_t TileView::roundTile(int32_t x, bool up) +{ + int32_t r = x % _tileSize; + + if (r == 0) { + return x; + } + + int32_t ret = x - r; + + if (up) { + ret += _tileSize; + } + + return ret; +} + +TileView::Point_t TileView::getOffset(const Point_t* point) +{ + Point_t offset; + offset.x = point->x - _pointTileCont[0].x; + offset.y = point->y - _pointTileCont[0].y; + return offset; +} diff --git a/x_track/src/App/Utils/TileView/TileView.h b/x_track/src/App/Utils/TileView/TileView.h new file mode 100644 index 0000000000000000000000000000000000000000..fb93bd6d3b309280f674b8450757272da7bb3c47 --- /dev/null +++ b/x_track/src/App/Utils/TileView/TileView.h @@ -0,0 +1,92 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __TILE_VIEW_H +#define __TILE_VIEW_H + +#include <stdint.h> + +class TileView { +public: + typedef struct Point { + Point(int32_t _x = 0, int32_t _y = 0) + : x(_x) + , y(_y) + { + } + int32_t x; + int32_t y; + } Point_t; + + typedef struct Rect { + Rect(int32_t _x = 0, int32_t _y = 0, int32_t _width = 0, int32_t _height = 0) + : x(_x) + , y(_y) + , width(_width) + , height(_height) + { + } + int32_t x; + int32_t y; + int32_t width; + int32_t height; + } Rect_t; + +public: + TileView(uint32_t viewWidth = 240, uint32_t viewHeight = 240, uint32_t tileSize = 256); + ~TileView() { } + + void setViewSize(uint32_t width, uint32_t height); + int32_t getViewWidth() + { + return _viewWidth; + } + int32_t getViewHeight() + { + return _viewHeight; + } + + void setFocusPos(int32_t x, int32_t y); + Point_t getFocusPos(); + Point_t getFocusOffset(); + + void setTileSize(uint32_t size); + uint32_t getTileSize(); + Rect_t getTileRect(); + Point_t getTileRectOffset(); + Point_t getTilePos(uint32_t index); + uint32_t getTileNum(); + + int32_t roundTile(int32_t x, bool up); + Point_t getOffset(const Point_t* point); + +private: + uint32_t _viewWidth; + uint32_t _viewHeight; + uint32_t _tileSize; + + Point_t _pointFocus; + Point_t _pointView[4]; + Point_t _pointTileCont[4]; +}; + +#endif /* __TILE_VIEW_H */ diff --git a/x_track/src/App/Utils/Time/DateStrings.cpp b/x_track/src/App/Utils/Time/DateStrings.cpp new file mode 100644 index 0000000000000000000000000000000000000000..489bb150c12d8cda16d47f0788f83620fb9d7c2a --- /dev/null +++ b/x_track/src/App/Utils/Time/DateStrings.cpp @@ -0,0 +1,97 @@ +/* DateStrings.cpp + * Definitions for date strings for use with the Time library + * + * Updated for Arduino 1.5.7 18 July 2014 + * + * No memory is consumed in the sketch if your code does not call any of the string methods + * You can change the text of the strings, make sure the short strings are each exactly 3 characters + * the long strings can be any length up to the constant dt_MAX_STRING_LEN defined in Time.h + * + */ + +#if defined(__AVR__) +#include <avr/pgmspace.h> +#else +// for compatiblity with Arduino Due and Teensy 3.0 and maybe others? +#define PROGMEM +#define PGM_P const char * +#define pgm_read_byte(addr) (*(const unsigned char *)(addr)) +#define pgm_read_word(addr) (*(const unsigned char **)(addr)) +#define strcpy_P(dest, src) strcpy((dest), (src)) +#endif +#include <string.h> // for strcpy_P or strcpy +#include "Time.h" + +// the short strings for each day or month must be exactly dt_SHORT_STR_LEN +#define dt_SHORT_STR_LEN 3 // the length of short strings + +static char buffer[dt_MAX_STRING_LEN+1]; // must be big enough for longest string and the terminating null + +const char monthStr0[] PROGMEM = ""; +const char monthStr1[] PROGMEM = "January"; +const char monthStr2[] PROGMEM = "February"; +const char monthStr3[] PROGMEM = "March"; +const char monthStr4[] PROGMEM = "April"; +const char monthStr5[] PROGMEM = "May"; +const char monthStr6[] PROGMEM = "June"; +const char monthStr7[] PROGMEM = "July"; +const char monthStr8[] PROGMEM = "August"; +const char monthStr9[] PROGMEM = "September"; +const char monthStr10[] PROGMEM = "October"; +const char monthStr11[] PROGMEM = "November"; +const char monthStr12[] PROGMEM = "December"; + +const PROGMEM char * const PROGMEM monthNames_P[] = +{ + monthStr0,monthStr1,monthStr2,monthStr3,monthStr4,monthStr5,monthStr6, + monthStr7,monthStr8,monthStr9,monthStr10,monthStr11,monthStr12 +}; + +const char monthShortNames_P[] PROGMEM = "ErrJanFebMarAprMayJunJulAugSepOctNovDec"; + +const char dayStr0[] PROGMEM = "Err"; +const char dayStr1[] PROGMEM = "Sunday"; +const char dayStr2[] PROGMEM = "Monday"; +const char dayStr3[] PROGMEM = "Tuesday"; +const char dayStr4[] PROGMEM = "Wednesday"; +const char dayStr5[] PROGMEM = "Thursday"; +const char dayStr6[] PROGMEM = "Friday"; +const char dayStr7[] PROGMEM = "Saturday"; + +const PROGMEM char * const PROGMEM dayNames_P[] = +{ + dayStr0,dayStr1,dayStr2,dayStr3,dayStr4,dayStr5,dayStr6,dayStr7 +}; + +const char dayShortNames_P[] PROGMEM = "ErrSunMonTueWedThuFriSat"; + +/* functions to return date strings */ + +char* monthStr(uint8_t month) +{ + strcpy_P(buffer, (PGM_P)pgm_read_word(&(monthNames_P[month]))); + return buffer; +} + +char* monthShortStr(uint8_t month) +{ + for (int i=0; i < dt_SHORT_STR_LEN; i++) + buffer[i] = pgm_read_byte(&(monthShortNames_P[i+ (month*dt_SHORT_STR_LEN)])); + buffer[dt_SHORT_STR_LEN] = 0; + return buffer; +} + +char* dayStr(uint8_t day) +{ + strcpy_P(buffer, (PGM_P)pgm_read_word(&(dayNames_P[day]))); + return buffer; +} + +char* dayShortStr(uint8_t day) +{ + uint8_t index = day*dt_SHORT_STR_LEN; + for (int i=0; i < dt_SHORT_STR_LEN; i++) + buffer[i] = pgm_read_byte(&(dayShortNames_P[index + i])); + buffer[dt_SHORT_STR_LEN] = 0; + return buffer; +} diff --git a/x_track/src/App/Utils/Time/Readme.txt b/x_track/src/App/Utils/Time/Readme.txt new file mode 100644 index 0000000000000000000000000000000000000000..67b148ecd07e1b8d3bb5306df46786d449e959ab --- /dev/null +++ b/x_track/src/App/Utils/Time/Readme.txt @@ -0,0 +1,131 @@ +Readme file for Arduino Time Library + +Time is a library that provides timekeeping functionality for Arduino. + +The code is derived from the Playground DateTime library but is updated +to provide an API that is more flexable and easier to use. + +A primary goal was to enable date and time functionality that can be used with +a variety of external time sources with minimum differences required in sketch logic. + +Example sketches illustrate how similar sketch code can be used with: a Real Time Clock, +internet NTP time service, GPS time data, and Serial time messages from a computer +for time synchronization. + +The functions available in the library include: + +hour(); // the hour now (0-23) +minute(); // the minute now (0-59) +second(); // the second now (0-59) +day(); // the day now (1-31) +weekday(); // day of the week, Sunday is day 0 +month(); // the month now (1-12) +year(); // the full four digit year: (2009, 2010 etc) + +there are also functions to return the hour in 12 hour format +hourFormat12(); // the hour now in 12 hour format +isAM(); // returns true if time now is AM +isPM(); // returns true if time now is PM + +now(); // returns the current time as seconds since Jan 1 1970 + +The time and date functions can take an optional parameter for the time. This prevents +errors if the time rolls over between elements. For example, if a new minute begins +between getting the minute and second, the values will be inconsistent. Using the +following functions eliminates this probglem + time_t t = now(); // store the current time in time variable t + hour(t); // returns the hour for the given time t + minute(t); // returns the minute for the given time t + second(t); // returns the second for the given time t + day(t); // the day for the given time t + weekday(t); // day of the week for the given time t + month(t); // the month for the given time t + year(t); // the year for the given time t + + +Functions for managing the timer services are: +setTime(t); // set the system time to the give time t +setTime(hr,min,sec,day,mnth,yr); // alternative to above, yr is 2 or 4 digit yr (2010 or 10 sets year to 2010) +adjustTime(adjustment); // adjust system time by adding the adjustment value + +timeStatus(); // indicates if time has been set and recently synchronized + // returns one of the following enumerations: + timeNotSet // the time has never been set, the clock started at Jan 1 1970 + timeNeedsSync // the time had been set but a sync attempt did not succeed + timeSet // the time is set and is synced +Time and Date values are not valid if the status is timeNotSet. Otherwise values can be used but +the returned time may have drifted if the status is timeNeedsSync. + +setSyncProvider(getTimeFunction); // set the external time provider +setSyncInterval(interval); // set the number of seconds between re-sync + + +There are many convenience macros in the time.h file for time constants and conversion of time units. + +To use the library, copy the download to the Library directory. + +The Time directory contains the Time library and some example sketches +illustrating how the library can be used with various time sources: + +- TimeSerial.pde shows Arduino as a clock without external hardware. + It is synchronized by time messages sent over the serial port. + A companion Processing sketch will automatically provide these messages + if it is running and connected to the Arduino serial port. + +- TimeSerialDateStrings.pde adds day and month name strings to the sketch above + Short (3 character) and long strings are available to print the days of + the week and names of the months. + +- TimeRTC uses a DS1307 real time clock to provide time synchronization. + A basic RTC library named DS1307RTC is included in the download. + To run this sketch the DS1307RTC library must be installed. + +- TimeRTCSet is similar to the above and adds the ability to set the Real Time Clock + +- TimeRTCLog demonstrates how to calculate the difference between times. + It is a vary simple logger application that monitors events on digtial pins + and prints (to the serial port) the time of an event and the time period since the previous event. + +- TimeNTP uses the Arduino Ethernet shield to access time using the internet NTP time service. + The NTP protocol uses UDP and the UdpBytewise library is required, see: + http://bitbucket.org/bjoern/arduino_osc/src/14667490521f/libraries/Ethernet/ + +- TimeGPS gets time from a GPS + This requires the TinyGPS library from Mikal Hart: + http://arduiniana.org/libraries/TinyGPS + +Differences between this code and the playground DateTime library +although the Time library is based on the DateTime codebase, the API has changed. +Changes in the Time library API: +- time elements are functions returning int (they are variables in DateTime) +- Years start from 1970 +- days of the week and months start from 1 (they start from 0 in DateTime) +- DateStrings do not require a seperate library +- time elements can be accessed non-atomically (in DateTime they are always atomic) +- function added to automatically sync time with extrnal source +- localTime and maketime parameters changed, localTime renamed to breakTime + +Technical notes: + +Internal system time is based on the standard Unix time_t. +The value is the number of seconds since Jan 1 1970. +System time begins at zero when the sketch starts. + +The internal time can be automatically synchronized at regular intervals to an external time source. +This is enabled by calling the setSyncProvider(provider) function - the provider argument is +the address of a function that returns the current time as a time_t. +See the sketches in the examples directory for usage. + +The default interval for re-syncing the time is 5 minutes but can be changed by calling the +setSyncInterval( interval) method to set the number of seconds between re-sync attempts. + +The Time library defines a structure for holding time elements that is a compact version of the C tm structure. +All the members of the Arduino tm structure are bytes and the year is offset from 1970. +Convenience macros provide conversion to and from the Arduino format. + +Low level functions to convert between system time and individual time elements are provided: + breakTime( time, &tm); // break time_t into elements stored in tm struct + makeTime( &tm); // return time_t from elements stored in tm struct + +The DS1307RTC library included in the download provides an example of how a time provider +can use the low level functions to interface with the Time library. diff --git a/x_track/src/App/Utils/Time/Time.cpp b/x_track/src/App/Utils/Time/Time.cpp new file mode 100644 index 0000000000000000000000000000000000000000..74db7bb5abb18b4a8999656406c78ecd3149e0f4 --- /dev/null +++ b/x_track/src/App/Utils/Time/Time.cpp @@ -0,0 +1,326 @@ +/* + time.c - low level time and date functions + Copyright (c) Michael Margolis 2009-2014 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + 1.0 6 Jan 2010 - initial release + 1.1 12 Feb 2010 - fixed leap year calculation error + 1.2 1 Nov 2010 - fixed setTime bug (thanks to Korman for this) + 1.3 24 Mar 2012 - many edits by Paul Stoffregen: fixed timeStatus() to update + status, updated examples for Arduino 1.0, fixed ARM + compatibility issues, added TimeArduinoDue and TimeTeensy3 + examples, add error checking and messages to RTC examples, + add examples to DS1307RTC library. + 1.4 5 Sep 2014 - compatibility with Arduino 1.5.7 +*/ + +#ifdef ARDUINO +# if ARDUINO >= 100 +# include <Arduino.h> +# else +# include <WProgram.h> +# endif +#else +# include "lvgl/lvgl.h" +# define millis() lv_tick_get() +#endif + +#include "Time.h" + +static tmElements_t g_tm; // a cache of time elements +static time_t g_cacheTime; // the time the cache was updated +static uint32_t g_syncInterval = 300; // time sync will be attempted after this many seconds + +void refreshCache(time_t t) { + if (t != g_cacheTime) { + breakTime(t, g_tm); + g_cacheTime = t; + } +} + +int hour() { // the hour now + return hour(now()); +} + +int hour(time_t t) { // the hour for the given time + refreshCache(t); + return g_tm.Hour; +} + +int hourFormat12() { // the hour now in 12 hour format + return hourFormat12(now()); +} + +int hourFormat12(time_t t) { // the hour for the given time in 12 hour format + refreshCache(t); + if( g_tm.Hour == 0 ) + return 12; // 12 midnight + else if( g_tm.Hour > 12) + return g_tm.Hour - 12 ; + else + return g_tm.Hour ; +} + +uint8_t isAM() { // returns true if time now is AM + return !isPM(now()); +} + +uint8_t isAM(time_t t) { // returns true if given time is AM + return !isPM(t); +} + +uint8_t isPM() { // returns true if PM + return isPM(now()); +} + +uint8_t isPM(time_t t) { // returns true if PM + return (hour(t) >= 12); +} + +int minute() { + return minute(now()); +} + +int minute(time_t t) { // the minute for the given time + refreshCache(t); + return g_tm.Minute; +} + +int second() { + return second(now()); +} + +int second(time_t t) { // the second for the given time + refreshCache(t); + return g_tm.Second; +} + +int day(){ + return(day(now())); +} + +int day(time_t t) { // the day for the given time (0-6) + refreshCache(t); + return g_tm.Day; +} + +int weekday() { // Sunday is day 1 + return weekday(now()); +} + +int weekday(time_t t) { + refreshCache(t); + return g_tm.Wday; +} + +int month(){ + return month(now()); +} + +int month(time_t t) { // the month for the given time + refreshCache(t); + return g_tm.Month; +} + +int year() { // as in Processing, the full four digit year: (2009, 2010 etc) + return year(now()); +} + +int year(time_t t) { // the year for the given time + refreshCache(t); + return tmYearToCalendar(g_tm.Year); +} + +/*============================================================================*/ +/* functions to convert to and from system time */ +/* These are for interfacing with time serivces and are not normally needed in a sketch */ + +// leap year calulator expects year argument as years offset from 1970 +#define LEAP_YEAR(Y) ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) ) + +static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0 + +void breakTime(time_t timeInput, tmElements_t &tm){ +// break the given time_t into time components +// this is a more compact version of the C library localtime function +// note that year is offset from 1970 !!! + + uint8_t year; + uint8_t month, monthLength; + uint32_t time; + unsigned long days; + + time = (uint32_t)timeInput; + tm.Second = time % 60; + time /= 60; // now it is minutes + tm.Minute = time % 60; + time /= 60; // now it is hours + tm.Hour = time % 24; + time /= 24; // now it is days + tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1 + + year = 0; + days = 0; + while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) { + year++; + } + tm.Year = year; // year is offset from 1970 + + days -= LEAP_YEAR(year) ? 366 : 365; + time -= days; // now it is days in this year, starting at 0 + + days=0; + month=0; + monthLength=0; + for (month=0; month<12; month++) { + if (month==1) { // february + if (LEAP_YEAR(year)) { + monthLength=29; + } else { + monthLength=28; + } + } else { + monthLength = monthDays[month]; + } + + if (time >= monthLength) { + time -= monthLength; + } else { + break; + } + } + tm.Month = month + 1; // jan is month 1 + tm.Day = time + 1; // day of month +} + +time_t makeTime(tmElements_t &tm){ +// assemble time elements into time_t +// note year argument is offset from 1970 (see macros in time.h to convert to other formats) +// previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9 + + int i; + uint32_t seconds; + + // seconds from 1970 till 1 jan 00:00:00 of the given year + seconds= tm.Year*(SECS_PER_DAY * 365); + for (i = 0; i < tm.Year; i++) { + if (LEAP_YEAR(i)) { + seconds += SECS_PER_DAY; // add extra days for leap years + } + } + + // add days for this year, months start from 1 + for (i = 1; i < tm.Month; i++) { + if ( (i == 2) && LEAP_YEAR(tm.Year)) { + seconds += SECS_PER_DAY * 29; + } else { + seconds += SECS_PER_DAY * monthDays[i-1]; //monthDay array starts from 0 + } + } + seconds+= (tm.Day-1) * SECS_PER_DAY; + seconds+= tm.Hour * SECS_PER_HOUR; + seconds+= tm.Minute * SECS_PER_MIN; + seconds+= tm.Second; + return (time_t)seconds; +} +/*=====================================================*/ +/* Low level system time functions */ + +static uint32_t sysTime = 0; +static uint32_t prevMillis = 0; +static uint32_t nextSyncTime = 0; +static timeStatus_t Status = timeNotSet; + +getExternalTime getTimePtr; // pointer to external sync function +//setExternalTime setTimePtr; // not used in this version + +#ifdef TIME_DRIFT_INFO // define this to get drift data +time_t sysUnsyncedTime = 0; // the time sysTime unadjusted by sync +#endif + + +time_t now() { + // calculate number of seconds passed since last call to now() + while (millis() - prevMillis >= 1000) { + // millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference + sysTime++; + prevMillis += 1000; +#ifdef TIME_DRIFT_INFO + sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift +#endif + } + if (nextSyncTime <= sysTime) { + if (getTimePtr != 0) { + time_t t = getTimePtr(); + if (t != 0) { + setTime(t); + } else { + nextSyncTime = sysTime + g_syncInterval; + Status = (Status == timeNotSet) ? timeNotSet : timeNeedsSync; + } + } + } + return (time_t)sysTime; +} + +void setTime(time_t t) { +#ifdef TIME_DRIFT_INFO + if(sysUnsyncedTime == 0) + sysUnsyncedTime = t; // store the time of the first call to set a valid Time +#endif + + sysTime = (uint32_t)t; + nextSyncTime = (uint32_t)t + g_syncInterval; + Status = timeSet; + prevMillis = millis(); // restart counting from now (thanks to Korman for this fix) +} + +void setTime(int hr,int min,int sec,int dy, int mnth, int yr){ + // year can be given as full four digit year or two digts (2010 or 10 for 2010); + //it is converted to years since 1970 + if( yr > 99) + yr = yr - 1970; + else + yr += 30; + g_tm.Year = yr; + g_tm.Month = mnth; + g_tm.Day = dy; + g_tm.Hour = hr; + g_tm.Minute = min; + g_tm.Second = sec; + setTime(makeTime(g_tm)); +} + +void adjustTime(long adjustment) { + sysTime += adjustment; +} + +// indicates if time has been set and recently synchronized +timeStatus_t timeStatus() { + now(); // required to actually update the status + return Status; +} + +void setSyncProvider( getExternalTime getTimeFunction){ + getTimePtr = getTimeFunction; + nextSyncTime = sysTime; + now(); // this will sync the clock +} + +void setSyncInterval(time_t interval){ // set the number of seconds between re-sync + g_syncInterval = (uint32_t)interval; + nextSyncTime = sysTime + g_syncInterval; +} diff --git a/x_track/src/App/Utils/Time/Time.h b/x_track/src/App/Utils/Time/Time.h new file mode 100644 index 0000000000000000000000000000000000000000..a79b0801ed2f4dcf3098112cd69bc219662265dc --- /dev/null +++ b/x_track/src/App/Utils/Time/Time.h @@ -0,0 +1 @@ +#include "TimeLib.h" diff --git a/x_track/src/App/Utils/Time/TimeLib.h b/x_track/src/App/Utils/Time/TimeLib.h new file mode 100644 index 0000000000000000000000000000000000000000..90b2fed575f46e93d9276dfe94b835633f2701bd --- /dev/null +++ b/x_track/src/App/Utils/Time/TimeLib.h @@ -0,0 +1,144 @@ +/* + time.h - low level time and date functions +*/ + +/* + July 3 2011 - fixed elapsedSecsThisWeek macro (thanks Vincent Valdy for this) + - fixed daysToTime_t macro (thanks maniacbug) +*/ + +#ifndef _Time_h +#ifdef __cplusplus +#define _Time_h + +#include <inttypes.h> +#if !defined(ARDUINO) +#include <sys/types.h> // for __time_t_defined, but avr libc lacks sys/types.h +#endif + + +#if !defined(__time_t_defined) && defined(ARDUINO) // avoid conflict with newlib or other posix libc +typedef unsigned long time_t; +#endif + + +// This ugly hack allows us to define C++ overloaded functions, when included +// from within an extern "C", as newlib's sys/stat.h does. Actually it is +// intended to include "time.h" from the C library (on ARM, but AVR does not +// have that file at all). On Mac and Windows, the compiler will find this +// "Time.h" instead of the C library "time.h", so we may cause other weird +// and unpredictable effects by conflicting with the C library header "time.h", +// but at least this hack lets us define C++ functions as intended. Hopefully +// nothing too terrible will result from overriding the C library header?! +extern "C++" { +typedef enum {timeNotSet, timeNeedsSync, timeSet +} timeStatus_t ; + +typedef enum { + dowInvalid, dowSunday, dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday +} timeDayOfWeek_t; + +typedef enum { + tmSecond, tmMinute, tmHour, tmWday, tmDay,tmMonth, tmYear, tmNbrFields +} tmByteFields; + +typedef struct { + uint8_t Second; + uint8_t Minute; + uint8_t Hour; + uint8_t Wday; // day of week, sunday is day 1 + uint8_t Day; + uint8_t Month; + uint8_t Year; // offset from 1970; +} tmElements_t, TimeElements, *tmElementsPtr_t; + +//convenience macros to convert to and from tm years +#define tmYearToCalendar(Y) ((Y) + 1970) // full four digit year +#define CalendarYrToTm(Y) ((Y) - 1970) +#define tmYearToY2k(Y) ((Y) - 30) // offset is from 2000 +#define y2kYearToTm(Y) ((Y) + 30) + +typedef time_t(*getExternalTime)(); +//typedef void (*setExternalTime)(const time_t); // not used in this version + + +/*==============================================================================*/ +/* Useful Constants */ +#define SECS_PER_MIN (60UL) +#define SECS_PER_HOUR (3600UL) +#define SECS_PER_DAY (SECS_PER_HOUR * 24UL) +#define DAYS_PER_WEEK (7UL) +#define SECS_PER_WEEK (SECS_PER_DAY * DAYS_PER_WEEK) +#define SECS_PER_YEAR (SECS_PER_WEEK * 52UL) +#define SECS_YR_2000 (946684800UL) // the time at the start of y2k + +/* Useful Macros for getting elapsed time */ +#define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN) +#define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN) +#define numberOfHours(_time_) (( _time_% SECS_PER_DAY) / SECS_PER_HOUR) +#define dayOfWeek(_time_) ((( _time_ / SECS_PER_DAY + 4) % DAYS_PER_WEEK)+1) // 1 = Sunday +#define elapsedDays(_time_) ( _time_ / SECS_PER_DAY) // this is number of days since Jan 1 1970 +#define elapsedSecsToday(_time_) (_time_ % SECS_PER_DAY) // the number of seconds since last midnight +// The following macros are used in calculating alarms and assume the clock is set to a date later than Jan 1 1971 +// Always set the correct time before settting alarms +#define previousMidnight(_time_) (( _time_ / SECS_PER_DAY) * SECS_PER_DAY) // time at the start of the given day +#define nextMidnight(_time_) ( previousMidnight(_time_) + SECS_PER_DAY ) // time at the end of the given day +#define elapsedSecsThisWeek(_time_) (elapsedSecsToday(_time_) + ((dayOfWeek(_time_)-1) * SECS_PER_DAY) ) // note that week starts on day 1 +#define previousSunday(_time_) (_time_ - elapsedSecsThisWeek(_time_)) // time at the start of the week for the given time +#define nextSunday(_time_) ( previousSunday(_time_)+SECS_PER_WEEK) // time at the end of the week for the given time + + +/* Useful Macros for converting elapsed time to a time_t */ +#define minutesToTime_t ((M)) ( (M) * SECS_PER_MIN) +#define hoursToTime_t ((H)) ( (H) * SECS_PER_HOUR) +#define daysToTime_t ((D)) ( (D) * SECS_PER_DAY) // fixed on Jul 22 2011 +#define weeksToTime_t ((W)) ( (W) * SECS_PER_WEEK) + +/*============================================================================*/ +/* time and date functions */ +int hour(); // the hour now +int hour(time_t t); // the hour for the given time +int hourFormat12(); // the hour now in 12 hour format +int hourFormat12(time_t t); // the hour for the given time in 12 hour format +uint8_t isAM(); // returns true if time now is AM +uint8_t isAM(time_t t); // returns true the given time is AM +uint8_t isPM(); // returns true if time now is PM +uint8_t isPM(time_t t); // returns true the given time is PM +int minute(); // the minute now +int minute(time_t t); // the minute for the given time +int second(); // the second now +int second(time_t t); // the second for the given time +int day(); // the day now +int day(time_t t); // the day for the given time +int weekday(); // the weekday now (Sunday is day 1) +int weekday(time_t t); // the weekday for the given time +int month(); // the month now (Jan is month 1) +int month(time_t t); // the month for the given time +int year(); // the full four digit year: (2009, 2010 etc) +int year(time_t t); // the year for the given time + +time_t now(); // return the current time as seconds since Jan 1 1970 +void setTime(time_t t); +void setTime(int hr,int min,int sec,int day, int month, int yr); +void adjustTime(long adjustment); + +/* date strings */ +#define dt_MAX_STRING_LEN 9 // length of longest date string (excluding terminating null) +char* monthStr(uint8_t month); +char* dayStr(uint8_t day); +char* monthShortStr(uint8_t month); +char* dayShortStr(uint8_t day); + +/* time sync functions */ +timeStatus_t timeStatus(); // indicates if time has been set and recently synchronized +void setSyncProvider( getExternalTime getTimeFunction); // identify the external time provider +void setSyncInterval(time_t interval); // set the number of seconds between re-sync + +/* low level functions to convert to and from system time */ +void breakTime(time_t time, tmElements_t &tm); // break time_t into elements +time_t makeTime(tmElements_t &tm); // convert time elements into time_t + +} // extern "C++" +#endif // __cplusplus +#endif /* _Time_h */ + diff --git a/x_track/src/App/Utils/Time/examples/Processing/SyncArduinoClock/SyncArduinoClock.pde b/x_track/src/App/Utils/Time/examples/Processing/SyncArduinoClock/SyncArduinoClock.pde new file mode 100644 index 0000000000000000000000000000000000000000..4313be33c2a4ccdbc2ad904ca41ab35a3e3fee1b --- /dev/null +++ b/x_track/src/App/Utils/Time/examples/Processing/SyncArduinoClock/SyncArduinoClock.pde @@ -0,0 +1,78 @@ +/** + * SyncArduinoClock. + * + * portIndex must be set to the port connected to the Arduino + * + * The current time is sent in response to request message from Arduino + * or by clicking the display window + * + * The time message is 11 ASCII text characters; a header (the letter 'T') + * followed by the ten digit system time (unix time) + */ + + +import processing.serial.*; +import java.util.Date; +import java.util.Calendar; +import java.util.GregorianCalendar; + +public static final short portIndex = 0; // select the com port, 0 is the first port +public static final String TIME_HEADER = "T"; //header for arduino serial time message +public static final char TIME_REQUEST = 7; // ASCII bell character +public static final char LF = 10; // ASCII linefeed +public static final char CR = 13; // ASCII linefeed +Serial myPort; // Create object from Serial class + +void setup() { + size(200, 200); + println(Serial.list()); + println(" Connecting to -> " + Serial.list()[portIndex]); + myPort = new Serial(this,Serial.list()[portIndex], 9600); + println(getTimeNow()); +} + +void draw() +{ + textSize(20); + textAlign(CENTER); + fill(0); + text("Click to send\nTime Sync", 0, 75, 200, 175); + if ( myPort.available() > 0) { // If data is available, + char val = char(myPort.read()); // read it and store it in val + if(val == TIME_REQUEST){ + long t = getTimeNow(); + sendTimeMessage(TIME_HEADER, t); + } + else + { + if(val == LF) + ; //igonore + else if(val == CR) + println(); + else + print(val); // echo everying but time request + } + } +} + +void mousePressed() { + sendTimeMessage( TIME_HEADER, getTimeNow()); +} + + +void sendTimeMessage(String header, long time) { + String timeStr = String.valueOf(time); + myPort.write(header); // send header and time to arduino + myPort.write(timeStr); + myPort.write('\n'); +} + +long getTimeNow(){ + // java time is in ms, we want secs + Date d = new Date(); + Calendar cal = new GregorianCalendar(); + long current = d.getTime()/1000; + long timezone = cal.get(cal.ZONE_OFFSET)/1000; + long daylight = cal.get(cal.DST_OFFSET)/1000; + return current + timezone + daylight; +} diff --git a/x_track/src/App/Utils/Time/examples/Processing/SyncArduinoClock/readme.txt b/x_track/src/App/Utils/Time/examples/Processing/SyncArduinoClock/readme.txt new file mode 100644 index 0000000000000000000000000000000000000000..da9721d7b9834bb20c0d938d05115b2eed14e536 --- /dev/null +++ b/x_track/src/App/Utils/Time/examples/Processing/SyncArduinoClock/readme.txt @@ -0,0 +1,9 @@ +SyncArduinoClock is a Processing sketch that responds to Arduino requests for +time synchronization messages. + +The portIndex must be set the Serial port connected to Arduino. + +Download TimeSerial.pde onto Arduino and you should see the time +message displayed when you run SyncArduinoClock in Processing. +The Arduino time is set from the time on your computer through the +Processing sketch. diff --git a/x_track/src/App/Utils/Time/examples/TimeArduinoDue/TimeArduinoDue.ino b/x_track/src/App/Utils/Time/examples/TimeArduinoDue/TimeArduinoDue.ino new file mode 100644 index 0000000000000000000000000000000000000000..f0a9a95df2dcce7d7f1e8908c87f9a6ad9e407db --- /dev/null +++ b/x_track/src/App/Utils/Time/examples/TimeArduinoDue/TimeArduinoDue.ino @@ -0,0 +1,71 @@ +/* + * TimeRTC.pde + * example code illustrating Time library with Real Time Clock. + * + * This example requires Markus Lange's Arduino Due RTC Library + * https://github.com/MarkusLange/Arduino-Due-RTC-Library + */ + +#include <TimeLib.h> +#include <rtc_clock.h> + +// Select the Slowclock source +//RTC_clock rtc_clock(RC); +RTC_clock rtc_clock(XTAL); + +void setup() { + Serial.begin(9600); + rtc_clock.init(); + if (rtc_clock.date_already_set() == 0) { + // Unfortunately, the Arduino Due hardware does not seem to + // be designed to maintain the RTC clock state when the + // board resets. Markus described it thusly: "Uhh the Due + // does reset with the NRSTB pin. This resets the full chip + // with all backup regions including RTC, RTT and SC. Only + // if the reset is done with the NRST pin will these regions + // stay with their old values." + rtc_clock.set_time(__TIME__); + rtc_clock.set_date(__DATE__); + // However, this might work on other unofficial SAM3X boards + // with different reset circuitry than Arduino Due? + } + setSyncProvider(getArduinoDueTime); + if(timeStatus()!= timeSet) + Serial.println("Unable to sync with the RTC"); + else + Serial.println("RTC has set the system time"); +} + +time_t getArduinoDueTime() +{ + return rtc_clock.unixtime(); +} + +void loop() +{ + digitalClockDisplay(); + delay(1000); +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/x_track/src/App/Utils/Time/examples/TimeGPS/TimeGPS.ino b/x_track/src/App/Utils/Time/examples/TimeGPS/TimeGPS.ino new file mode 100644 index 0000000000000000000000000000000000000000..fea9698867b35abf43e35bdb4fb41d69a01f27dc --- /dev/null +++ b/x_track/src/App/Utils/Time/examples/TimeGPS/TimeGPS.ino @@ -0,0 +1,87 @@ +/* + * TimeGPS.pde + * example code illustrating time synced from a GPS + * + */ + +#include <TimeLib.h> +#include <TinyGPS.h> // http://arduiniana.org/libraries/TinyGPS/ +#include <SoftwareSerial.h> +// TinyGPS and SoftwareSerial libraries are the work of Mikal Hart + +SoftwareSerial SerialGPS = SoftwareSerial(10, 11); // receive on pin 10 +TinyGPS gps; + +// To use a hardware serial port, which is far more efficient than +// SoftwareSerial, uncomment this line and remove SoftwareSerial +//#define SerialGPS Serial1 + +// Offset hours from gps time (UTC) +const int offset = 1; // Central European Time +//const int offset = -5; // Eastern Standard Time (USA) +//const int offset = -4; // Eastern Daylight Time (USA) +//const int offset = -8; // Pacific Standard Time (USA) +//const int offset = -7; // Pacific Daylight Time (USA) + +// Ideally, it should be possible to learn the time zone +// based on the GPS position data. However, that would +// require a complex library, probably incorporating some +// sort of database using Eric Muller's time zone shape +// maps, at http://efele.net/maps/tz/ + +time_t prevDisplay = 0; // when the digital clock was displayed + +void setup() +{ + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + SerialGPS.begin(4800); + Serial.println("Waiting for GPS time ... "); +} + +void loop() +{ + while (SerialGPS.available()) { + if (gps.encode(SerialGPS.read())) { // process gps messages + // when TinyGPS reports new data... + unsigned long age; + int Year; + byte Month, Day, Hour, Minute, Second; + gps.crack_datetime(&Year, &Month, &Day, &Hour, &Minute, &Second, NULL, &age); + if (age < 500) { + // set the Time to the latest GPS reading + setTime(Hour, Minute, Second, Day, Month, Year); + adjustTime(offset * SECS_PER_HOUR); + } + } + } + if (timeStatus()!= timeNotSet) { + if (now() != prevDisplay) { //update the display only if the time has changed + prevDisplay = now(); + digitalClockDisplay(); + } + } +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits) { + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/x_track/src/App/Utils/Time/examples/TimeNTP/TimeNTP.ino b/x_track/src/App/Utils/Time/examples/TimeNTP/TimeNTP.ino new file mode 100644 index 0000000000000000000000000000000000000000..17a908f821b7f6978d98f84e5918a8d8af5345d8 --- /dev/null +++ b/x_track/src/App/Utils/Time/examples/TimeNTP/TimeNTP.ino @@ -0,0 +1,135 @@ +/* + * Time_NTP.pde + * Example showing time sync to NTP time source + * + * This sketch uses the Ethernet library + */ + +#include <TimeLib.h> +#include <Ethernet.h> +#include <EthernetUdp.h> +#include <SPI.h> + +byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; +// NTP Servers: +IPAddress timeServer(132, 163, 4, 101); // time-a.timefreq.bldrdoc.gov +// IPAddress timeServer(132, 163, 4, 102); // time-b.timefreq.bldrdoc.gov +// IPAddress timeServer(132, 163, 4, 103); // time-c.timefreq.bldrdoc.gov + + +const int timeZone = 1; // Central European Time +//const int timeZone = -5; // Eastern Standard Time (USA) +//const int timeZone = -4; // Eastern Daylight Time (USA) +//const int timeZone = -8; // Pacific Standard Time (USA) +//const int timeZone = -7; // Pacific Daylight Time (USA) + + +EthernetUDP Udp; +unsigned int localPort = 8888; // local port to listen for UDP packets + +void setup() +{ + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + delay(250); + Serial.println("TimeNTP Example"); + if (Ethernet.begin(mac) == 0) { + // no point in carrying on, so do nothing forevermore: + while (1) { + Serial.println("Failed to configure Ethernet using DHCP"); + delay(10000); + } + } + Serial.print("IP number assigned by DHCP is "); + Serial.println(Ethernet.localIP()); + Udp.begin(localPort); + Serial.println("waiting for sync"); + setSyncProvider(getNtpTime); +} + +time_t prevDisplay = 0; // when the digital clock was displayed + +void loop() +{ + if (timeStatus() != timeNotSet) { + if (now() != prevDisplay) { //update the display only if time has changed + prevDisplay = now(); + digitalClockDisplay(); + } + } +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +/*-------- NTP code ----------*/ + +const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message +byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets + +time_t getNtpTime() +{ + while (Udp.parsePacket() > 0) ; // discard any previously received packets + Serial.println("Transmit NTP Request"); + sendNTPpacket(timeServer); + uint32_t beginWait = millis(); + while (millis() - beginWait < 1500) { + int size = Udp.parsePacket(); + if (size >= NTP_PACKET_SIZE) { + Serial.println("Receive NTP Response"); + Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer + unsigned long secsSince1900; + // convert four bytes starting at location 40 to a long integer + secsSince1900 = (unsigned long)packetBuffer[40] << 24; + secsSince1900 |= (unsigned long)packetBuffer[41] << 16; + secsSince1900 |= (unsigned long)packetBuffer[42] << 8; + secsSince1900 |= (unsigned long)packetBuffer[43]; + return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; + } + } + Serial.println("No NTP Response :-("); + return 0; // return 0 if unable to get the time +} + +// send an NTP request to the time server at the given address +void sendNTPpacket(IPAddress &address) +{ + // set all bytes in the buffer to 0 + memset(packetBuffer, 0, NTP_PACKET_SIZE); + // Initialize values needed to form NTP request + // (see URL above for details on the packets) + packetBuffer[0] = 0b11100011; // LI, Version, Mode + packetBuffer[1] = 0; // Stratum, or type of clock + packetBuffer[2] = 6; // Polling Interval + packetBuffer[3] = 0xEC; // Peer Clock Precision + // 8 bytes of zero for Root Delay & Root Dispersion + packetBuffer[12] = 49; + packetBuffer[13] = 0x4E; + packetBuffer[14] = 49; + packetBuffer[15] = 52; + // all NTP fields have been given values, now + // you can send a packet requesting a timestamp: + Udp.beginPacket(address, 123); //NTP requests are to port 123 + Udp.write(packetBuffer, NTP_PACKET_SIZE); + Udp.endPacket(); +} + diff --git a/x_track/src/App/Utils/Time/examples/TimeNTP_ESP8266WiFi/TimeNTP_ESP8266WiFi.ino b/x_track/src/App/Utils/Time/examples/TimeNTP_ESP8266WiFi/TimeNTP_ESP8266WiFi.ino new file mode 100644 index 0000000000000000000000000000000000000000..cab64883fa1362ddae939e8c11406ba8c8f7f05f --- /dev/null +++ b/x_track/src/App/Utils/Time/examples/TimeNTP_ESP8266WiFi/TimeNTP_ESP8266WiFi.ino @@ -0,0 +1,143 @@ +/* + * Time_NTP.pde + * Example showing time sync to NTP time source + * + * This sketch uses the ESP8266WiFi library + */ + +#include <TimeLib.h> +#include <ESP8266WiFi.h> +#include <WiFiUdp.h> + +const char ssid[] = "*************"; // your network SSID (name) +const char pass[] = "********"; // your network password + +// NTP Servers: +IPAddress timeServer(132, 163, 4, 101); // time-a.timefreq.bldrdoc.gov +// IPAddress timeServer(132, 163, 4, 102); // time-b.timefreq.bldrdoc.gov +// IPAddress timeServer(132, 163, 4, 103); // time-c.timefreq.bldrdoc.gov + + +const int timeZone = 1; // Central European Time +//const int timeZone = -5; // Eastern Standard Time (USA) +//const int timeZone = -4; // Eastern Daylight Time (USA) +//const int timeZone = -8; // Pacific Standard Time (USA) +//const int timeZone = -7; // Pacific Daylight Time (USA) + + +WiFiUDP Udp; +unsigned int localPort = 8888; // local port to listen for UDP packets + +void setup() +{ + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + delay(250); + Serial.println("TimeNTP Example"); + Serial.print("Connecting to "); + Serial.println(ssid); + WiFi.begin(ssid, pass); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + + Serial.print("IP number assigned by DHCP is "); + Serial.println(WiFi.localIP()); + Serial.println("Starting UDP"); + Udp.begin(localPort); + Serial.print("Local port: "); + Serial.println(Udp.localPort()); + Serial.println("waiting for sync"); + setSyncProvider(getNtpTime); +} + +time_t prevDisplay = 0; // when the digital clock was displayed + +void loop() +{ + if (timeStatus() != timeNotSet) { + if (now() != prevDisplay) { //update the display only if time has changed + prevDisplay = now(); + digitalClockDisplay(); + } + } +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print("."); + Serial.print(month()); + Serial.print("."); + Serial.print(year()); + Serial.println(); +} + + + +void printDigits(int digits){ + // utility for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +/*-------- NTP code ----------*/ + +const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message +byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets + +time_t getNtpTime() +{ + while (Udp.parsePacket() > 0) ; // discard any previously received packets + Serial.println("Transmit NTP Request"); + sendNTPpacket(timeServer); + uint32_t beginWait = millis(); + while (millis() - beginWait < 1500) { + int size = Udp.parsePacket(); + if (size >= NTP_PACKET_SIZE) { + Serial.println("Receive NTP Response"); + Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer + unsigned long secsSince1900; + // convert four bytes starting at location 40 to a long integer + secsSince1900 = (unsigned long)packetBuffer[40] << 24; + secsSince1900 |= (unsigned long)packetBuffer[41] << 16; + secsSince1900 |= (unsigned long)packetBuffer[42] << 8; + secsSince1900 |= (unsigned long)packetBuffer[43]; + return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; + } + } + Serial.println("No NTP Response :-("); + return 0; // return 0 if unable to get the time +} + +// send an NTP request to the time server at the given address +void sendNTPpacket(IPAddress &address) +{ + // set all bytes in the buffer to 0 + memset(packetBuffer, 0, NTP_PACKET_SIZE); + // Initialize values needed to form NTP request + // (see URL above for details on the packets) + packetBuffer[0] = 0b11100011; // LI, Version, Mode + packetBuffer[1] = 0; // Stratum, or type of clock + packetBuffer[2] = 6; // Polling Interval + packetBuffer[3] = 0xEC; // Peer Clock Precision + // 8 bytes of zero for Root Delay & Root Dispersion + packetBuffer[12] = 49; + packetBuffer[13] = 0x4E; + packetBuffer[14] = 49; + packetBuffer[15] = 52; + // all NTP fields have been given values, now + // you can send a packet requesting a timestamp: + Udp.beginPacket(address, 123); //NTP requests are to port 123 + Udp.write(packetBuffer, NTP_PACKET_SIZE); + Udp.endPacket(); +} diff --git a/x_track/src/App/Utils/Time/examples/TimeRTC/TimeRTC.ino b/x_track/src/App/Utils/Time/examples/TimeRTC/TimeRTC.ino new file mode 100644 index 0000000000000000000000000000000000000000..fa10ff6f58d278901dbae84db306b1403e353497 --- /dev/null +++ b/x_track/src/App/Utils/Time/examples/TimeRTC/TimeRTC.ino @@ -0,0 +1,55 @@ +/* + * TimeRTC.pde + * example code illustrating Time library with Real Time Clock. + * + */ + +#include <TimeLib.h> +#include <Wire.h> +#include <DS1307RTC.h> // a basic DS1307 library that returns time as a time_t + +void setup() { + Serial.begin(9600); + while (!Serial) ; // wait until Arduino Serial Monitor opens + setSyncProvider(RTC.get); // the function to get the time from the RTC + if(timeStatus()!= timeSet) + Serial.println("Unable to sync with the RTC"); + else + Serial.println("RTC has set the system time"); +} + +void loop() +{ + if (timeStatus() == timeSet) { + digitalClockDisplay(); + } else { + Serial.println("The time has not been set. Please run the Time"); + Serial.println("TimeRTCSet example, or DS1307RTC SetTime example."); + Serial.println(); + delay(4000); + } + delay(1000); +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/x_track/src/App/Utils/Time/examples/TimeRTCLog/TimeRTCLog.ino b/x_track/src/App/Utils/Time/examples/TimeRTCLog/TimeRTCLog.ino new file mode 100644 index 0000000000000000000000000000000000000000..0b250c2020ba21f27fec919bf0a0be04378094a4 --- /dev/null +++ b/x_track/src/App/Utils/Time/examples/TimeRTCLog/TimeRTCLog.ino @@ -0,0 +1,107 @@ +/* + * TimeRTCLogger.pde + * example code illustrating adding and subtracting Time. + * + * this sketch logs pin state change events + * the time of the event and time since the previous event is calculated and sent to the serial port. + */ + +#include <TimeLib.h> +#include <Wire.h> +#include <DS1307RTC.h> // a basic DS1307 library that returns time as a time_t + +const int nbrInputPins = 6; // monitor 6 digital pins +const int inputPins[nbrInputPins] = {2,3,4,5,6,7}; // pins to monitor +boolean state[nbrInputPins] ; // the state of the monitored pins +time_t prevEventTime[nbrInputPins] ; // the time of the previous event + +void setup() { + Serial.begin(9600); + setSyncProvider(RTC.get); // the function to sync the time from the RTC + for(int i=0; i < nbrInputPins; i++){ + pinMode( inputPins[i], INPUT); + // uncomment these lines if pull-up resistors are wanted + // pinMode( inputPins[i], INPUT_PULLUP); + // state[i] = HIGH; + } +} + +void loop() +{ + for(int i=0; i < nbrInputPins; i++) + { + boolean val = digitalRead(inputPins[i]); + if(val != state[i]) + { + time_t duration = 0; // the time since the previous event + state[i] = val; + time_t timeNow = now(); + if(prevEventTime[i] > 0) + // if this was not the first state change, calculate the time from the previous change + duration = duration = timeNow - prevEventTime[i]; + logEvent(inputPins[i], val, timeNow, duration ); // log the event + prevEventTime[i] = timeNow; // store the time for this event + } + } +} + +void logEvent( int pin, boolean state, time_t timeNow, time_t duration) +{ + Serial.print("Pin "); + Serial.print(pin); + if( state == HIGH) + Serial.print(" went High at "); + else + Serial.print(" went Low at "); + showTime(timeNow); + if(duration > 0){ + // only display duration if greater than 0 + Serial.print(", Duration was "); + showDuration(duration); + } + Serial.println(); +} + + +void showTime(time_t t){ + // display the given time + Serial.print(hour(t)); + printDigits(minute(t)); + printDigits(second(t)); + Serial.print(" "); + Serial.print(day(t)); + Serial.print(" "); + Serial.print(month(t)); + Serial.print(" "); + Serial.print(year(t)); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +void showDuration(time_t duration){ +// prints the duration in days, hours, minutes and seconds + if(duration >= SECS_PER_DAY){ + Serial.print(duration / SECS_PER_DAY); + Serial.print(" day(s) "); + duration = duration % SECS_PER_DAY; + } + if(duration >= SECS_PER_HOUR){ + Serial.print(duration / SECS_PER_HOUR); + Serial.print(" hour(s) "); + duration = duration % SECS_PER_HOUR; + } + if(duration >= SECS_PER_MIN){ + Serial.print(duration / SECS_PER_MIN); + Serial.print(" minute(s) "); + duration = duration % SECS_PER_MIN; + } + Serial.print(duration); + Serial.print(" second(s) "); +} + diff --git a/x_track/src/App/Utils/Time/examples/TimeRTCSet/TimeRTCSet.ino b/x_track/src/App/Utils/Time/examples/TimeRTCSet/TimeRTCSet.ino new file mode 100644 index 0000000000000000000000000000000000000000..49c49f3740cdcade51d5d4aa4236729ba72c8f5b --- /dev/null +++ b/x_track/src/App/Utils/Time/examples/TimeRTCSet/TimeRTCSet.ino @@ -0,0 +1,80 @@ +/* + * TimeRTCSet.pde + * example code illustrating Time library with Real Time Clock. + * + * RTC clock is set in response to serial port time message + * A Processing example sketch to set the time is included in the download + * On Linux, you can use "date +T%s > /dev/ttyACM0" (UTC time zone) + */ + +#include <TimeLib.h> +#include <Wire.h> +#include <DS1307RTC.h> // a basic DS1307 library that returns time as a time_t + + +void setup() { + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + setSyncProvider(RTC.get); // the function to get the time from the RTC + if (timeStatus() != timeSet) + Serial.println("Unable to sync with the RTC"); + else + Serial.println("RTC has set the system time"); +} + +void loop() +{ + if (Serial.available()) { + time_t t = processSyncMessage(); + if (t != 0) { + RTC.set(t); // set the RTC and the system time to the received value + setTime(t); + } + } + digitalClockDisplay(); + delay(1000); +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +/* code to process time sync messages from the serial port */ +#define TIME_HEADER "T" // Header tag for serial time sync message + +unsigned long processSyncMessage() { + unsigned long pctime = 0L; + const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 + + if(Serial.find(TIME_HEADER)) { + pctime = Serial.parseInt(); + return pctime; + if( pctime < DEFAULT_TIME) { // check the value is a valid time (greater than Jan 1 2013) + pctime = 0L; // return 0 to indicate that the time is not valid + } + } + return pctime; +} + + + + + diff --git a/x_track/src/App/Utils/Time/examples/TimeSerial/TimeSerial.ino b/x_track/src/App/Utils/Time/examples/TimeSerial/TimeSerial.ino new file mode 100644 index 0000000000000000000000000000000000000000..07e609fde171e5a467bf4292d86cfd9d40077de6 --- /dev/null +++ b/x_track/src/App/Utils/Time/examples/TimeSerial/TimeSerial.ino @@ -0,0 +1,81 @@ +/* + * TimeSerial.pde + * example code illustrating Time library set through serial port messages. + * + * Messages consist of the letter T followed by ten digit time (as seconds since Jan 1 1970) + * you can send the text on the next line using Serial Monitor to set the clock to noon Jan 1 2013 + T1357041600 + * + * A Processing example sketch to automatically send the messages is included in the download + * On Linux, you can use "date +T%s\n > /dev/ttyACM0" (UTC time zone) + */ + +#include <TimeLib.h> + +#define TIME_HEADER "T" // Header tag for serial time sync message +#define TIME_REQUEST 7 // ASCII bell character requests a time sync message + +void setup() { + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + pinMode(13, OUTPUT); + setSyncProvider( requestSync); //set function to call when sync required + Serial.println("Waiting for sync message"); +} + +void loop(){ + if (Serial.available()) { + processSyncMessage(); + } + if (timeStatus()!= timeNotSet) { + digitalClockDisplay(); + } + if (timeStatus() == timeSet) { + digitalWrite(13, HIGH); // LED on if synced + } else { + digitalWrite(13, LOW); // LED off if needs refresh + } + delay(1000); +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + + +void processSyncMessage() { + unsigned long pctime; + const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 + + if(Serial.find(TIME_HEADER)) { + pctime = Serial.parseInt(); + if( pctime >= DEFAULT_TIME) { // check the integer is a valid time (greater than Jan 1 2013) + setTime(pctime); // Sync Arduino clock to the time received on the serial port + } + } +} + +time_t requestSync() +{ + Serial.write(TIME_REQUEST); + return 0; // the time will be sent later in response to serial mesg +} + diff --git a/x_track/src/App/Utils/Time/examples/TimeSerialDateStrings/TimeSerialDateStrings.ino b/x_track/src/App/Utils/Time/examples/TimeSerialDateStrings/TimeSerialDateStrings.ino new file mode 100644 index 0000000000000000000000000000000000000000..95d2568c1763e37b3293f4bccdcecdf0c2b6cd20 --- /dev/null +++ b/x_track/src/App/Utils/Time/examples/TimeSerialDateStrings/TimeSerialDateStrings.ino @@ -0,0 +1,108 @@ +/* + * TimeSerialDateStrings.pde + * example code illustrating Time library date strings + * + * This sketch adds date string functionality to TimeSerial sketch + * Also shows how to handle different messages + * + * A message starting with a time header sets the time + * A Processing example sketch to automatically send the messages is inclided in the download + * On Linux, you can use "date +T%s\n > /dev/ttyACM0" (UTC time zone) + * + * A message starting with a format header sets the date format + + * send: Fs\n for short date format + * send: Fl\n for long date format + */ + +#include <TimeLib.h> + +// single character message tags +#define TIME_HEADER 'T' // Header tag for serial time sync message +#define FORMAT_HEADER 'F' // Header tag indicating a date format message +#define FORMAT_SHORT 's' // short month and day strings +#define FORMAT_LONG 'l' // (lower case l) long month and day strings + +#define TIME_REQUEST 7 // ASCII bell character requests a time sync message + +static boolean isLongFormat = true; + +void setup() { + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + setSyncProvider( requestSync); //set function to call when sync required + Serial.println("Waiting for sync message"); +} + +void loop(){ + if (Serial.available() > 1) { // wait for at least two characters + char c = Serial.read(); + if( c == TIME_HEADER) { + processSyncMessage(); + } + else if( c== FORMAT_HEADER) { + processFormatMessage(); + } + } + if (timeStatus()!= timeNotSet) { + digitalClockDisplay(); + } + delay(1000); +} + +void digitalClockDisplay() { + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + if(isLongFormat) + Serial.print(dayStr(weekday())); + else + Serial.print(dayShortStr(weekday())); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + if(isLongFormat) + Serial.print(monthStr(month())); + else + Serial.print(monthShortStr(month())); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits) { + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +void processFormatMessage() { + char c = Serial.read(); + if( c == FORMAT_LONG){ + isLongFormat = true; + Serial.println(F("Setting long format")); + } + else if( c == FORMAT_SHORT) { + isLongFormat = false; + Serial.println(F("Setting short format")); + } +} + +void processSyncMessage() { + unsigned long pctime; + const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 - paul, perhaps we define in time.h? + + pctime = Serial.parseInt(); + if( pctime >= DEFAULT_TIME) { // check the integer is a valid time (greater than Jan 1 2013) + setTime(pctime); // Sync Arduino clock to the time received on the serial port + } +} + +time_t requestSync() { + Serial.write(TIME_REQUEST); + return 0; // the time will be sent later in response to serial mesg +} diff --git a/x_track/src/App/Utils/Time/examples/TimeTeensy3/TimeTeensy3.ino b/x_track/src/App/Utils/Time/examples/TimeTeensy3/TimeTeensy3.ino new file mode 100644 index 0000000000000000000000000000000000000000..f68dd8cc7f2ecdeb9ebd14f96720fe8a110252d7 --- /dev/null +++ b/x_track/src/App/Utils/Time/examples/TimeTeensy3/TimeTeensy3.ino @@ -0,0 +1,78 @@ +/* + * TimeRTC.pde + * example code illustrating Time library with Real Time Clock. + * + */ + +#include <TimeLib.h> + +void setup() { + // set the Time library to use Teensy 3.0's RTC to keep time + setSyncProvider(getTeensy3Time); + + Serial.begin(115200); + while (!Serial); // Wait for Arduino Serial Monitor to open + delay(100); + if (timeStatus()!= timeSet) { + Serial.println("Unable to sync with the RTC"); + } else { + Serial.println("RTC has set the system time"); + } +} + +void loop() { + if (Serial.available()) { + time_t t = processSyncMessage(); + if (t != 0) { + Teensy3Clock.set(t); // set the RTC + setTime(t); + } + } + digitalClockDisplay(); + delay(1000); +} + +void digitalClockDisplay() { + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +time_t getTeensy3Time() +{ + return Teensy3Clock.get(); +} + +/* code to process time sync messages from the serial port */ +#define TIME_HEADER "T" // Header tag for serial time sync message + +unsigned long processSyncMessage() { + unsigned long pctime = 0L; + const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 + + if(Serial.find(TIME_HEADER)) { + pctime = Serial.parseInt(); + return pctime; + if( pctime < DEFAULT_TIME) { // check the value is a valid time (greater than Jan 1 2013) + pctime = 0L; // return 0 to indicate that the time is not valid + } + } + return pctime; +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/x_track/src/App/Utils/Time/keywords.txt b/x_track/src/App/Utils/Time/keywords.txt new file mode 100644 index 0000000000000000000000000000000000000000..073f8f8855020d95241a84adad53b20b443bdc75 --- /dev/null +++ b/x_track/src/App/Utils/Time/keywords.txt @@ -0,0 +1,34 @@ +####################################### +# Syntax Coloring Map For Time +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### +time_t KEYWORD1 +####################################### +# Methods and Functions (KEYWORD2) +####################################### +now KEYWORD2 +second KEYWORD2 +minute KEYWORD2 +hour KEYWORD2 +day KEYWORD2 +month KEYWORD2 +year KEYWORD2 +isAM KEYWORD2 +isPM KEYWORD2 +weekday KEYWORD2 +setTime KEYWORD2 +adjustTime KEYWORD2 +setSyncProvider KEYWORD2 +setSyncInterval KEYWORD2 +timeStatus KEYWORD2 +TimeLib KEYWORD2 +####################################### +# Instances (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/x_track/src/App/Utils/Time/library.json b/x_track/src/App/Utils/Time/library.json new file mode 100644 index 0000000000000000000000000000000000000000..071e74c9faae40afe31f4ef0c69d5fdad17b3c14 --- /dev/null +++ b/x_track/src/App/Utils/Time/library.json @@ -0,0 +1,22 @@ +{ +"name": "Time", +"frameworks": "Arduino", +"keywords": "Time, date, hour, minute, second, day, week, month, year, RTC", +"description": "Time keeping library", +"url": "http://playground.arduino.cc/Code/Time", +"authors": +[ +{ + "name": "Michael Margolis" +}, +{ + "name": "Paul Stoffregen" +} +], +"repository": +{ + "type": "git", + "url": "https://github.com/PaulStoffregen/Time" +} +} + diff --git a/x_track/src/App/Utils/Time/library.properties b/x_track/src/App/Utils/Time/library.properties new file mode 100644 index 0000000000000000000000000000000000000000..49b1e2a16e8215d7e63ffa8a50e4af41569c9116 --- /dev/null +++ b/x_track/src/App/Utils/Time/library.properties @@ -0,0 +1,10 @@ +name=Time +version=1.5 +author=Michael Margolis +maintainer=Paul Stoffregen +sentence=Timekeeping functionality for Arduino +paragraph=Date and Time functions, with provisions to synchronize to external time sources like GPS and NTP (Internet). This library is often used together with TimeAlarms and DS1307RTC. +category=Timing +url=http://playground.arduino.cc/code/time +architectures=* + diff --git a/x_track/src/App/Utils/TrackLineFilter/TrackLineFilter.cpp b/x_track/src/App/Utils/TrackLineFilter/TrackLineFilter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6f8e3ca30920c6816d00122759f02ac7544008dd --- /dev/null +++ b/x_track/src/App/Utils/TrackLineFilter/TrackLineFilter.cpp @@ -0,0 +1,207 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "TrackLineFilter.h" +#include <math.h> +#include <string.h> + +#define SQ(x) ((x) * (x)) + +#define TLF_USE_LOG 0 + +#if TLF_USE_LOG +#include <stdio.h> +#define LOG_PRINT(format, ...) printf(format, ##__VA_ARGS__), printf("\n") +#else +#define LOG_PRINT(format, ...) +#endif + +TrackLineFilter::TrackLineFilter(Callback_t callback, void* userData) + : _outputCallback(callback) + , _userData(userData) + , _clipArea { 0 } + , _prePoint { 0 } + , _pointCnt(0) + , _pointOutputCnt(0) + , _isFirstPoint(false) + , _isInArea(true) +{ +} + +TrackLineFilter::~TrackLineFilter() +{ +} + +void TrackLineFilter::reset() +{ + _pointCnt = 0; + _pointOutputCnt = 0; + _isFirstPoint = false; + _isInArea = false; +} + +void TrackLineFilter::pushPoint(const Point_t* point) +{ + checkPoint(point); + next(point); +} + +void TrackLineFilter::pushEnd() +{ + const Point_t* p = _pointCnt > 0 ? &_prePoint : nullptr; + outputPoint(p); +} + +void TrackLineFilter::setArea(const Area_t* area) +{ + _clipArea = *area; +} + +bool TrackLineFilter::getIsPointInArea(const Point_t* point) +{ + bool inArea = (point->x >= _clipArea.x0 + && point->x <= _clipArea.x1 + && point->y >= _clipArea.y0 + && point->y <= _clipArea.y1); + + LOG_PRINT( + "# Point(%d,%d) Area(%d,%d # %d,%d) inArea:%d", + point->x, point->y, + _clipArea.x0, _clipArea.y0, + _clipArea.x1, _clipArea.y1, + inArea); + + return inArea; +} + +void TrackLineFilter::outputPoint(const Point_t* point) +{ + if (!_isInArea) { + sendEvent(EVENT_ID::START_LINE, nullptr); + _isInArea = true; + } + sendEvent(EVENT_ID::APPEND_POINT, point); + _pointOutputCnt++; +} + +void TrackLineFilter::next(const Point_t* point) +{ + _prePoint = *point; + _pointCnt++; +} + +bool TrackLineFilter::checkPoint(const Point_t* point) +{ + /* if line start, record first point */ + if (!_isFirstPoint) { + _isFirstPoint = true; + return false; + } + + /* if two point all in area */ + if (getIsPointInArea(&_prePoint) && getIsPointInArea(point)) { + outputPoint(point); + return true; + } + + /* + + p1 *------------------* p2 + | | + | | + | | + | | + | | + | | + | | + p4 *------------------* p3 + + */ + + Point_t p1; + p1.x = _clipArea.x0; + p1.y = _clipArea.y0; + + Point_t p2; + p2.x = _clipArea.x1; + p2.y = _clipArea.y0; + + Point_t p3; + p3.x = _clipArea.x1; + p3.y = _clipArea.y1; + + Point_t p4; + p4.x = _clipArea.x0; + p4.y = _clipArea.y1; + + Point_t q1 = _prePoint; + Point_t q2 = *point; + + /* check if line intersect with area */ + if (isIntersect(&p1, &p2, &q1, &q2) + || isIntersect(&p2, &p3, &q1, &q2) + || isIntersect(&p3, &p4, &q1, &q2) + || isIntersect(&p4, &p1, &q1, &q2)) { + outputPoint(&_prePoint); + outputPoint(point); + return true; + } + + return false; +} + +void TrackLineFilter::sendEvent(EVENT_ID id, const Point_t* point) +{ + if (!_outputCallback) { + return; + } + + Event_t event; + event.eventID = id; + event.point = point; + _outputCallback(this, &event); +} + +int64_t TrackLineFilter::crossProduct(const Point_t* a, const Point_t* b) +{ + return (int64_t)a->x * (int64_t)b->y - (int64_t)b->x * (int64_t)a->y; +} + +bool TrackLineFilter::isIntersect(const Point_t* p1, const Point_t* p2, const Point_t* q1, const Point_t* q2) +{ + Point_t u = { p2->x - p1->x, p2->y - p1->y }; + Point_t v = { q2->x - q1->x, q2->y - q1->y }; + Point_t w = { p1->x - q1->x, p1->y - q1->y }; + int64_t t = crossProduct(&u, &v); + int64_t s1 = crossProduct(&w, &u); + int64_t s2 = crossProduct(&w, &v); + + if (t > 0) { + return s1 >= 0 && s1 <= t && s2 >= 0 && s2 <= t; + } + + if (t < 0) { + return s1 <= 0 && s1 >= t && s2 <= 0 && s2 >= t; + } + + return s1 == 0 || s2 == 0; +} diff --git a/x_track/src/App/Utils/TrackLineFilter/TrackLineFilter.h b/x_track/src/App/Utils/TrackLineFilter/TrackLineFilter.h new file mode 100644 index 0000000000000000000000000000000000000000..71a7ad14ca86defa2e5ddf94108dc20071a3cbcc --- /dev/null +++ b/x_track/src/App/Utils/TrackLineFilter/TrackLineFilter.h @@ -0,0 +1,96 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __TRACK_LINE_FILTER_H +#define __TRACK_LINE_FILTER_H + +#include <stdint.h> + +class TrackLineFilter { +public: + typedef struct + { + int32_t x; + int32_t y; + } Point_t; + + enum class EVENT_ID { + START_LINE, + APPEND_POINT, + }; + + typedef struct + { + EVENT_ID eventID; + uint32_t lineIndex; + const Point_t* point; + } Event_t; + + typedef struct + { + int32_t x0; + int32_t y0; + int32_t x1; + int32_t y1; + } Area_t; + + typedef void (*Callback_t)(TrackLineFilter* filter, const Event_t* event); + +public: +public: + TrackLineFilter(Callback_t callback, void* userData); + ~TrackLineFilter(); + + void reset(); + void pushPoint(int32_t x, int32_t y) + { + Point_t point = { x, y }; + pushPoint(&point); + } + void pushPoint(const Point_t* point); + void pushEnd(); + + void setArea(const Area_t* area); + const Area_t* getArea() { return &_clipArea; } + bool getIsPointInArea(const Point_t* point); + void* getUserData() { return _userData; } + +private: + Callback_t _outputCallback; + void* _userData; + Area_t _clipArea; + Point_t _prePoint; + uint32_t _pointCnt; + uint32_t _pointOutputCnt; + bool _isFirstPoint; + bool _isInArea; + +private: + void next(const Point_t* point); + bool checkPoint(const Point_t* point); + void outputPoint(const Point_t* point); + void sendEvent(EVENT_ID id, const Point_t* point); + int64_t crossProduct(const Point_t* a, const Point_t* b); + bool isIntersect(const Point_t* p1, const Point_t* p2, const Point_t* q1, const Point_t* q2); +}; + +#endif diff --git a/x_track/src/App/Utils/TrackPointFilter/TrackPointFilter.cpp b/x_track/src/App/Utils/TrackPointFilter/TrackPointFilter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f4945ded70a6a4c4bdbf0f7ab3fa31d49e78d269 --- /dev/null +++ b/x_track/src/App/Utils/TrackPointFilter/TrackPointFilter.cpp @@ -0,0 +1,280 @@ +/* + * MIT License + * Copyright (c) 2021 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "TrackPointFilter.h" +#include <cmath> +#include <float.h> +#include <string.h> + +#define SQ(x) ((x) * (x)) +#define FLOAT_0 FLT_EPSILON + +#define TPF_USE_LOG 0 +#define TPF_USE_LINE_CHECKED 0 + +#if TPF_USE_LOG +#include <stdio.h> +#define LOG_PRINT(format, ...) printf(format, ##__VA_ARGS__), printf("\n") +#else +#define LOG_PRINT(format, ...) +#endif + +TrackPointFilter::TrackPointFilter(Callback_t callback, void* userData, double offsetThr) + : _outputCallback(callback) + , _userData(userData) + , _offsetThreshold(offsetThr) + , _tailPoint { 0 } + , _prePoint { 0 } + , _refLine { 0 } + , _pointCnt(0) + , _pointOutputCnt(0) + , _secondFilterMode(false) +{ +} + +TrackPointFilter::~TrackPointFilter() +{ +} + +void TrackPointFilter::reset() +{ + _pointCnt = 0; + _pointOutputCnt = 0; +} + +bool TrackPointFilter::pushPoint(const Point_t* point) +{ + bool retval = false; + dumpPoint("\n+P", point); + if (_pointCnt == 0) { + retval = true; + outputPoint(point); + } else if (_pointCnt == 1) { + if (!getLine(&_refLine, &_prePoint, point)) { + return false; + } + dumpLine("First", &_refLine); + } else { + dumpLine("--", &_refLine); + + double offset = getOffset(&_refLine, point); + LOG_PRINT("OFS = %lf", offset); + + if (offset > _offsetThreshold) { + LOG_PRINT("<---> offset detect!"); + + retval = true; + + if (_secondFilterMode) { + outputPoint(&_tailPoint); + } + + outputPoint(&_prePoint); + if (!getLine(&_refLine, &_prePoint, point)) { + return false; + } + } else { + Line_t line; + if (!getLine(&line, &_tailPoint, &_prePoint)) { + return false; + } + + dumpLine("L", &line); +#if TPF_USE_LINE_CHECKED + bool inLine1 = getIsPointInLine(&line, &_tailPoint); + if (!inLine1) { + dumpPoint("tailPoint", &_tailPoint); + LOG_PRINT("not in L"); + while (1) { } + } + + bool inLine2 = getIsPointInLine(&line, &_prePoint); + if (!inLine2) { + dumpPoint("prePoint", &_prePoint); + LOG_PRINT("not in L"); + while (1) { } + } +#endif + + Line_t verLine; + getVerticalLine(&verLine, &line, &_prePoint); + + dumpLine("|", &verLine); + dumpPoint("in", &_prePoint); + +#if TPF_USE_LINE_CHECKED + bool inLine3 = getIsPointInLine(&verLine, &_prePoint); + if (!inLine3) { + dumpPoint("prePoint", &_prePoint); + LOG_PRINT("not in verLine"); + while (1) { } + } +#endif + + if (getIsOnSameSide(&verLine, &_tailPoint, point)) { + LOG_PRINT("~~~ direction change detect!"); + + dumpPoint("p0", &_tailPoint); + dumpPoint("p1", &_prePoint); + dumpPoint("p2", point); + + retval = true; + + if (_secondFilterMode) { + outputPoint(&_tailPoint); + } + + outputPoint(&_prePoint); + if (!getLine(&_refLine, &_prePoint, point)) { + return false; + } + } + } + } + + _tailPoint = _prePoint; + _prePoint = *point; + _pointCnt++; + + return retval; +} + +void TrackPointFilter::pushEnd() +{ + outputPoint(&_prePoint); + reset(); +} + +void TrackPointFilter::setSecondFilterModeEnable(bool en) +{ + _secondFilterMode = en; +} + +void TrackPointFilter::outputPoint(const Point_t* point) +{ + if (_outputCallback) { + dumpPoint(">>> output", point); + LOG_PRINT(" "); + _outputCallback(this, point); + } + _pointOutputCnt++; +} + +bool TrackPointFilter::getLine(Line_t* line, const Point_t* point0, const Point_t* point1) +{ + bool retval = true; + + double x0 = point0->x; + double x1 = point1->x; + double y0 = point0->y; + double y1 = point1->y; + + double x_diff_abs = std::abs(x0 - x1); + double y_diff_abs = std::abs(y0 - y1); + + double a = 0; + double b = 0; + double c = 0; + + if (x_diff_abs < FLOAT_0 && y_diff_abs > FLOAT_0) { + a = 1; + b = 0; + c = -x0; + } else if (x_diff_abs > FLOAT_0 && y_diff_abs < FLOAT_0) { + a = 0; + b = 1; + c = -y0; + } else if (x_diff_abs > FLOAT_0 && y_diff_abs > FLOAT_0) { + a = (y1 - y0) / (x0 - x1); + b = (a * (x0 - x1)) / (y1 - y0); + c = 0 - a * x0 - b * y0; + } else { + retval = false; + } + + line->a = a; + line->b = b; + line->c = c; + + return retval; +} + +void TrackPointFilter::dumpLine(const char* name, const Line_t* line) +{ + LOG_PRINT( + "%s : %lfx + %lfy + %lf = 0 { y = %lfx + %lf }", + name, line->a, line->b, line->c, + -line->a / line->b, -line->c / line->b); +} + +void TrackPointFilter::dumpPoint(const char* name, const Point_t* point) +{ + LOG_PRINT("%s : (%lf, %lf)", name, point->x, point->y); +} + +void TrackPointFilter::getVerticalLine(Line_t* verLine, const Line_t* oriLine, const Point_t* point) +{ + verLine->a = -oriLine->b; + verLine->b = oriLine->a; + verLine->c = 0 - verLine->a * point->x - verLine->b * point->y; +} + +double TrackPointFilter::getOffset(const Line_t* line, const Point_t* point) +{ + double temp = line->a * point->x + line->b * point->y + line->c; + double offset = std::abs(temp) * invSqrt(SQ(line->a) + SQ(line->b)); + return offset; +} + +bool TrackPointFilter::getIsOnSameSide(const Line_t* line, const Point_t* point0, const Point_t* point1) +{ + bool retval = true; + double side = (line->a * point0->x + line->b * point0->y + line->c) + * (line->a * point1->x + line->b * point1->y + line->c); + + if (side < FLOAT_0) { + retval = false; + } + + return retval; +} + +bool TrackPointFilter::getIsPointInLine(const Line_t* line, const Point_t* point) +{ + double result = line->a * point->x + line->b * point->y + line->c; + return std::abs(result) < FLOAT_0; +} + +double TrackPointFilter::invSqrt(double num) +{ + uint32_t i; + float x2, y; + const float threehalfs = 1.5f; + x2 = (float)num * 0.5f; + y = (float)num; + i = *(uint32_t*)&y; + i = 0x5f3759df - (i >> 1); + y = *(float*)&i; + y = y * (threehalfs - (x2 * y * y)); + y = y * (threehalfs - (x2 * y * y)); + return y; +} diff --git a/x_track/src/App/Utils/TrackPointFilter/TrackPointFilter.h b/x_track/src/App/Utils/TrackPointFilter/TrackPointFilter.h new file mode 100644 index 0000000000000000000000000000000000000000..ab74c3b02c694f338b340325fc787846c1640216 --- /dev/null +++ b/x_track/src/App/Utils/TrackPointFilter/TrackPointFilter.h @@ -0,0 +1,94 @@ +/* + * MIT License + * Copyright (c) 2021 - 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __TRACK_POINT_FILTER_H +#define __TRACK_POINT_FILTER_H + +#include <stdint.h> + +class TrackPointFilter { +public: + typedef struct + { + double x; + double y; + } Point_t; + + typedef void (*Callback_t)(TrackPointFilter* filter, const Point_t* point); + +public: + TrackPointFilter(Callback_t callback = nullptr, void* userData = nullptr, double offsetThr = 2); + ~TrackPointFilter(); + + void reset(); + bool pushPoint(double x, double y) + { + Point_t point = { x, y }; + return pushPoint(&point); + } + bool pushPoint(const Point_t* point); + void pushEnd(); + + void setSecondFilterModeEnable(bool en); + void getCounts(uint32_t* sum, uint32_t* output) + { + *sum = _pointCnt; + *output = _pointOutputCnt; + } + void* getUserData() + { + return _userData; + } + +private: + typedef struct + { + double a; + double b; + double c; + } Line_t; + +private: + Callback_t _outputCallback; + void* _userData; + double _offsetThreshold; + Point_t _tailPoint; + Point_t _prePoint; + Line_t _refLine; + uint32_t _pointCnt; + uint32_t _pointOutputCnt; + bool _secondFilterMode; + +private: + bool getLine(Line_t* line, const Point_t* point0, const Point_t* point1); + void getVerticalLine(Line_t* verLine, const Line_t* oriLine, const Point_t* point); + double getOffset(const Line_t* line, const Point_t* point); + bool getIsOnSameSide(const Line_t* line, const Point_t* point0, const Point_t* point1); + bool getIsPointInLine(const Line_t* line, const Point_t* point); + + void dumpLine(const char* name, const Line_t* line); + void dumpPoint(const char* name, const Point_t* point); + void outputPoint(const Point_t* point); + double invSqrt(double num); +}; + +#endif diff --git a/x_track/src/App/Utils/TrackView/TrackView.cpp b/x_track/src/App/Utils/TrackView/TrackView.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3b9865eaa47557ad38252249396ba2dfe3a0d640 --- /dev/null +++ b/x_track/src/App/Utils/TrackView/TrackView.cpp @@ -0,0 +1,203 @@ +/* + * MIT License + * Copyright (c) 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "TrackView.h" +#include "Utils/PointContainer/PointContainer.h" +#include "Utils/lv_poly_line/lv_poly_line.h" + +TrackView::TrackView(lv_obj_t* parent) + : _cont(nullptr) + , _trackLine(nullptr) + , _activeLine(nullptr) + , _activePoint { 0 } + , _pointContainer(nullptr) + , _refLevel(0) + , _curLevel(0) + , _pointFilter(onPointFilterEvent, &_lineFilter) + , _lineFilter(onLineFilterEvent, this) +{ + _cont = lv_obj_create(parent); + lv_obj_remove_style_all(_cont); + lv_obj_clear_flag(_cont, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_clear_flag(_cont, LV_OBJ_FLAG_CLICKABLE); + lv_obj_add_flag(_cont, LV_OBJ_FLAG_IGNORE_LAYOUT); + lv_obj_add_flag(_cont, LV_OBJ_FLAG_HIDDEN); + + _trackLine = new lv_poly_line(_cont); + _activeLine = lv_line_create(_cont); + lv_obj_add_flag(_activeLine, LV_OBJ_FLAG_HIDDEN); + + _pointFilter.setSecondFilterModeEnable(true); +} + +TrackView::~TrackView() +{ + delete _trackLine; + lv_obj_del(_cont); +} + +void TrackView::setStyle(lv_style_t* style) +{ + _trackLine->set_style(style); + lv_obj_add_style(_activeLine, style, 0); + // lv_obj_set_style_line_color(_activeLine, lv_palette_main(LV_PALETTE_BLUE), 0); +} + +void TrackView::setArea(int32_t x0, int32_t y0, int32_t x1, int32_t y1) +{ + lv_obj_set_size(_cont, x1 - x0 + 1, y1 - y0 + 1); + + TrackLineFilter::Area_t area = { x0, y0, x1, y1 }; + _lineFilter.setArea(&area); +} + +void TrackView::setActivePoint(int32_t x, int32_t y) +{ + if (!_pointContainer) { + return; + } + + lv_point_precise_t end; + if (!_trackLine->get_end_point(&end)) { + lv_obj_add_flag(_activeLine, LV_OBJ_FLAG_HIDDEN); + return; + } + + const TrackLineFilter::Area_t* area = _lineFilter.getArea(); + + TrackLineFilter::Point_t relEndPoint = { area->x0 + (int32_t)end.x, area->y0 + (int32_t)end.y }; + auto relActivePoint = convertPoint(x, y, _refLevel, _curLevel); + + if (!_lineFilter.getIsPointInArea(&relEndPoint) + && !_lineFilter.getIsPointInArea((TrackLineFilter::Point_t*)&relActivePoint)) { + lv_obj_add_flag(_activeLine, LV_OBJ_FLAG_HIDDEN); + LV_LOG_INFO("Point out of area"); + return; + } + + auto offset = getOffset(relActivePoint.x, relActivePoint.y); + _activePoint[0].x = end.x; + _activePoint[0].y = end.y; + _activePoint[1].x = offset.x; + _activePoint[1].y = offset.y; + + lv_line_set_points(_activeLine, _activePoint, 2); + lv_obj_clear_flag(_activeLine, LV_OBJ_FLAG_HIDDEN); +} + +void TrackView::pushPoint(int32_t x, int32_t y) +{ + if (!_pointContainer) { + return; + } + + auto point = convertPoint(x, y, _refLevel, _curLevel); + _pointFilter.pushPoint(point.x, point.y); +} + +void TrackView::setLevel(int level) +{ + if (level == _curLevel) { + return; + } + + _curLevel = level; +} + +void TrackView::setPointContainer(PointContainer* pointCont, int refLevel) +{ + _pointContainer = pointCont; + _refLevel = refLevel; +} + +bool TrackView::reload() +{ + if (!_pointContainer) { + lv_obj_add_flag(_cont, LV_OBJ_FLAG_HIDDEN); + return false; + } + + _pointFilter.reset(); + _lineFilter.reset(); + _trackLine->reset(); + + int32_t x, y; + _pointContainer->popStart(); + while (_pointContainer->popPoint(&x, &y)) { + pushPoint(x, y); + } + + lv_obj_add_flag(_activeLine, LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(_cont, LV_OBJ_FLAG_HIDDEN); + return true; +} + +TrackView::Point_t TrackView::convertPoint(int32_t srcX, int32_t srcY, int srcLevel, int destLevel) +{ + Point_t point; + + int diff = srcLevel - destLevel; + + if (diff == 0) { + return { srcX, srcY }; + } + + if (diff > 0) { + point.x = srcX >> diff; + point.y = srcY >> diff; + } else { + point.x = srcX << -diff; + point.y = srcY << -diff; + } + + return point; +} + +TrackView::Point_t TrackView::getOffset(int32_t x, int32_t y) +{ + const TrackLineFilter::Area_t* area = _lineFilter.getArea(); + return { x - area->x0, y - area->y0 }; +} + +void TrackView::onPointFilterEvent(TrackPointFilter* filter, const TrackPointFilter::Point_t* point) +{ + auto lineFilter = (TrackLineFilter*)filter->getUserData(); + lineFilter->pushPoint(point->x, point->y); +} + +void TrackView::onLineFilterEvent(TrackLineFilter* filter, const TrackLineFilter::Event_t* event) +{ + auto self = (TrackView*)filter->getUserData(); + + switch (event->eventID) { + case TrackLineFilter::EVENT_ID::START_LINE: + self->_trackLine->start(); + break; + case TrackLineFilter::EVENT_ID::APPEND_POINT: { + Point_t offset = self->getOffset(event->point->x, event->point->y); + self->_trackLine->append(offset.x, offset.y); + } break; + default: + break; + } +} diff --git a/x_track/src/App/Utils/TrackView/TrackView.h b/x_track/src/App/Utils/TrackView/TrackView.h new file mode 100644 index 0000000000000000000000000000000000000000..a24812ab0bae55dcadc63e71cab7c0caeff9d66e --- /dev/null +++ b/x_track/src/App/Utils/TrackView/TrackView.h @@ -0,0 +1,75 @@ +/* + * MIT License + * Copyright (c) 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __TRACK_VIEW_H +#define __TRACK_VIEW_H + +#include "Utils/TrackLineFilter/TrackLineFilter.h" +#include "Utils/TrackPointFilter/TrackPointFilter.h" +#include "lvgl/lvgl.h" + +class PointContainer; +class lv_poly_line; + +class TrackView { +public: + TrackView(lv_obj_t* parent); + ~TrackView(); + + void setStyle(lv_style_t* style); + void setArea(int32_t x0, int32_t y0, int32_t x1, int32_t y1); + void setActivePoint(int32_t x, int32_t y); + void pushPoint(int32_t x, int32_t y); + void setLevel(int level); + void setPointContainer(PointContainer* pointCont, int refLevel); + bool reload(); + lv_obj_t* getViewCont() { return _cont; } + int getLevel() { return _curLevel; } + int getRefLevel() { return _refLevel; } + +private: + typedef struct + { + int32_t x; + int32_t y; + } Point_t; + +private: + lv_obj_t* _cont; + lv_poly_line* _trackLine; + lv_obj_t* _activeLine; + lv_point_precise_t _activePoint[2]; + PointContainer* _pointContainer; + int _refLevel; + int _curLevel; + + TrackPointFilter _pointFilter; + TrackLineFilter _lineFilter; + +private: + Point_t getOffset(int32_t x, int32_t y); + Point_t convertPoint(int32_t srcX, int32_t srcY, int srcLevel, int destLevel); + static void onPointFilterEvent(TrackPointFilter* filter, const TrackPointFilter::Point_t* point); + static void onLineFilterEvent(TrackLineFilter* filter, const TrackLineFilter::Event_t* event); +}; + +#endif // __TRACK_VIEW_H diff --git a/x_track/src/App/Utils/WString/WCharacter.h b/x_track/src/App/Utils/WString/WCharacter.h new file mode 100644 index 0000000000000000000000000000000000000000..fbde2265d050a01f4c4540cde0a97553e5f9d355 --- /dev/null +++ b/x_track/src/App/Utils/WString/WCharacter.h @@ -0,0 +1,172 @@ +/* + WCharacter.h - Character utility functions for Wiring & Arduino + Copyright (c) 2010 Hernando Barragan. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef Character_h +#define Character_h + +#include <ctype.h> + +//#define isascii(c) ((unsigned int)c < 0x7F) +//#define toascii(c) ((unsigned char)c) +#define isblank(C) (c==' '||c=='\t') + +// WCharacter.h prototypes +inline bool isAlphaNumeric(int c); +inline bool isAlpha(int c); +inline bool isAscii(int c); +inline bool isWhitespace(int c); +inline bool isControl(int c); +inline bool isDigit(int c); +inline bool isGraph(int c); +inline bool isLowerCase(int c); +inline bool isPrintable(int c); +inline bool isPunct(int c); +inline bool isSpace(int c); +inline bool isUpperCase(int c); +inline bool isHexadecimalDigit(int c); +inline int toAscii(int c); +inline int toLowerCase(int c); +inline int toUpperCase(int c); + + +// Checks for an alphanumeric character. +// It is equivalent to (isalpha(c) || isdigit(c)). +inline bool isAlphaNumeric(int c) +{ + return ( isalnum(c) == 0 ? false : true); +} + + +// Checks for an alphabetic character. +// It is equivalent to (isupper(c) || islower(c)). +inline bool isAlpha(int c) +{ + return ( isalpha(c) == 0 ? false : true); +} + + +// Checks whether c is a 7-bit unsigned char value +// that fits into the ASCII character set. +inline bool isAscii(int c) +{ + return ( isascii (c) == 0 ? false : true); +} + + +// Checks for a blank character, that is, a space or a tab. +inline bool isWhitespace(int c) +{ + return ( isblank (c) == 0 ? false : true); +} + + +// Checks for a control character. +inline bool isControl(int c) +{ + return ( iscntrl (c) == 0 ? false : true); +} + + +// Checks for a digit (0 through 9). +inline bool isDigit(int c) +{ + return ( isdigit (c) == 0 ? false : true); +} + + +// Checks for any printable character except space. +inline bool isGraph(int c) +{ + return ( isgraph (c) == 0 ? false : true); +} + + +// Checks for a lower-case character. +inline bool isLowerCase(int c) +{ + return (islower (c) == 0 ? false : true); +} + + +// Checks for any printable character including space. +inline bool isPrintable(int c) +{ + return ( isprint (c) == 0 ? false : true); +} + + +// Checks for any printable character which is not a space +// or an alphanumeric character. +inline bool isPunct(int c) +{ + return ( ispunct (c) == 0 ? false : true); +} + + +// Checks for white-space characters. For the avr-libc library, +// these are: space, formfeed ('\f'), newline ('\n'), carriage +// return ('\r'), horizontal tab ('\t'), and vertical tab ('\v'). +inline bool isSpace(int c) +{ + return ( isspace (c) == 0 ? false : true); +} + + +// Checks for an uppercase letter. +inline bool isUpperCase(int c) +{ + return ( isupper (c) == 0 ? false : true); +} + + +// Checks for a hexadecimal digits, i.e. one of 0 1 2 3 4 5 6 7 +// 8 9 a b c d e f A B C D E F. +inline bool isHexadecimalDigit(int c) +{ + return ( isxdigit (c) == 0 ? false : true); +} + + +// Converts c to a 7-bit unsigned char value that fits into the +// ASCII character set, by clearing the high-order bits. +inline int toAscii(int c) +{ + return toascii (c); +} + + +// Warning: +// Many people will be unhappy if you use this function. +// This function will convert accented letters into random +// characters. + +// Converts the letter c to lower case, if possible. +inline int toLowerCase(int c) +{ + return tolower (c); +} + + +// Converts the letter c to upper case, if possible. +inline int toUpperCase(int c) +{ + return toupper (c); +} + +#endif diff --git a/x_track/src/App/Utils/WString/WString.cpp b/x_track/src/App/Utils/WString/WString.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3c97bf880c1fa87c92b3ef618e3738368efb06b5 --- /dev/null +++ b/x_track/src/App/Utils/WString/WString.cpp @@ -0,0 +1,823 @@ +/* + WString.cpp - String library for Wiring & Arduino + ...mostly rewritten by Paul Stoffregen... + Copyright (c) 2009-10 Hernando Barragan. All rights reserved. + Copyright 2011, Paul Stoffregen, paul@pjrc.com + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "WString.h" + +#define WS_USE_STD_LIB 0 + +#if WS_USE_STD_LIB +# define WS_MEM_REALLOC(ptr, new_size) realloc(ptr, new_size) +# define WS_MEM_FREE(ptr) free(ptr) +#else +#include "lvgl/lvgl.h" +# define WS_MEM_REALLOC(ptr, new_size) lv_realloc(ptr, new_size) +# define WS_MEM_FREE(ptr) lv_free(ptr) +#endif + +#ifdef WIN32 +# define ultoa _ultoa_s +# define ltoa _ltoa_s +# define itoa _itoa_s +#else +# include "itoa.h" +#endif + +/*********************************************/ +/* Constructors */ +/*********************************************/ + +String::String(const char *cstr) +{ + init(); + if (cstr) copy(cstr, strlen(cstr)); +} + +String::String(const String &value) +{ + init(); + *this = value; +} + +String::String(const __FlashStringHelper *pstr) +{ + init(); + *this = pstr; +} + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +String::String(String &&rval) +{ + init(); + move(rval); +} +String::String(StringSumHelper &&rval) +{ + init(); + move(rval); +} +#endif + +String::String(char c) +{ + init(); + char buf[2]; + buf[0] = c; + buf[1] = 0; + *this = buf; +} + +String::String(unsigned char value, unsigned char base) +{ + init(); + char buf[1 + 8 * sizeof(unsigned char)]; + ultoa(value, buf, base); + *this = buf; +} + +String::String(int value, unsigned char base) +{ + init(); + char buf[2 + 8 * sizeof(int)]; + itoa(value, buf, base); + *this = buf; +} + +String::String(unsigned int value, unsigned char base) +{ + init(); + char buf[1 + 8 * sizeof(unsigned int)]; + ultoa(value, buf, base); + *this = buf; +} + +String::String(long value, unsigned char base) +{ + init(); + char buf[2 + 8 * sizeof(long)]; + ltoa(value, buf, base); + *this = buf; +} + +String::String(unsigned long value, unsigned char base) +{ + init(); + char buf[1 + 8 * sizeof(unsigned long)]; + ultoa(value, buf, base); + *this = buf; +} + +String::String(float value, unsigned char decimalPlaces) +{ + init(); + char buf[33]; + *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf); +} + +String::String(double value, unsigned char decimalPlaces) +{ + init(); + char buf[33]; + *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf); +} + +String::~String() +{ + WS_MEM_FREE(buffer); +} + +/*********************************************/ +/* Memory Management */ +/*********************************************/ + +inline void String::init(void) +{ + buffer = NULL; + capacity = 0; + len = 0; +} + +void String::invalidate(void) +{ + if (buffer) WS_MEM_FREE(buffer); + buffer = NULL; + capacity = len = 0; +} + +unsigned char String::reserve(unsigned int size) +{ + if (buffer && capacity >= size) return 1; + if (changeBuffer(size)) + { + if (len == 0) buffer[0] = 0; + return 1; + } + return 0; +} + +unsigned char String::changeBuffer(unsigned int maxStrLen) +{ + char *newbuffer = (char *)WS_MEM_REALLOC(buffer, maxStrLen + 1); + if (newbuffer) + { + buffer = newbuffer; + capacity = maxStrLen; + return 1; + } + return 0; +} + +/*********************************************/ +/* Copy and Move */ +/*********************************************/ + +String & String::copy(const char *cstr, unsigned int length) +{ + if (!reserve(length)) + { + invalidate(); + return *this; + } + len = length; + strcpy(buffer, cstr); + return *this; +} + +String & String::copy(const __FlashStringHelper *pstr, unsigned int length) +{ + if (!reserve(length)) + { + invalidate(); + return *this; + } + len = length; + strcpy(buffer, (PGM_P)pstr);//strcpy_P + return *this; +} + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +void String::move(String &rhs) +{ + if (buffer) + { + if (rhs && capacity >= rhs.len) + { + strcpy(buffer, rhs.buffer); + len = rhs.len; + rhs.len = 0; + return; + } + else + { + WS_MEM_FREE(buffer); + } + } + buffer = rhs.buffer; + capacity = rhs.capacity; + len = rhs.len; + rhs.buffer = NULL; + rhs.capacity = 0; + rhs.len = 0; +} +#endif + +String & String::operator = (const String &rhs) +{ + if (this == &rhs) return *this; + + if (rhs.buffer) copy(rhs.buffer, rhs.len); + else invalidate(); + + return *this; +} + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +String & String::operator = (String &&rval) +{ + if (this != &rval) move(rval); + return *this; +} + +String & String::operator = (StringSumHelper &&rval) +{ + if (this != &rval) move(rval); + return *this; +} +#endif + +String & String::operator = (const char *cstr) +{ + if (cstr) copy(cstr, strlen(cstr)); + else invalidate(); + + return *this; +} + +String & String::operator = (const __FlashStringHelper *pstr) +{ + if (pstr) copy(pstr, strlen((PGM_P)pstr));//strlen_P + else invalidate(); + + return *this; +} + +/*********************************************/ +/* concat */ +/*********************************************/ + +unsigned char String::concat(const String &s) +{ + return concat(s.buffer, s.len); +} + +unsigned char String::concat(const char *cstr, unsigned int length) +{ + unsigned int newlen = len + length; + if (!cstr) return 0; + if (length == 0) return 1; + if (!reserve(newlen)) return 0; + strcpy(buffer + len, cstr); + len = newlen; + return 1; +} + +unsigned char String::concat(const char *cstr) +{ + if (!cstr) return 0; + return concat(cstr, strlen(cstr)); +} + +unsigned char String::concat(char c) +{ + char buf[2]; + buf[0] = c; + buf[1] = 0; + return concat(buf, 1); +} + +unsigned char String::concat(unsigned char num) +{ + char buf[1 + 3 * sizeof(unsigned char)]; + itoa(num, buf, 10); + return concat(buf, strlen(buf)); +} + +unsigned char String::concat(int num) +{ + char buf[2 + 3 * sizeof(int)]; + itoa(num, buf, 10); + return concat(buf, strlen(buf)); +} + +unsigned char String::concat(unsigned int num) +{ + char buf[1 + 3 * sizeof(unsigned int)]; + ultoa(num, buf, 10); + return concat(buf, strlen(buf)); +} + +unsigned char String::concat(long num) +{ + char buf[2 + 3 * sizeof(long)]; + ltoa(num, buf, 10); + return concat(buf, strlen(buf)); +} + +unsigned char String::concat(unsigned long num) +{ + char buf[1 + 3 * sizeof(unsigned long)]; + ultoa(num, buf, 10); + return concat(buf, strlen(buf)); +} + +unsigned char String::concat(float num) +{ + char buf[20]; + char* string = dtostrf(num, 4, 2, buf); + return concat(string, strlen(string)); +} + +unsigned char String::concat(double num) +{ + char buf[20]; + char* string = dtostrf(num, 4, 2, buf); + return concat(string, strlen(string)); +} + +unsigned char String::concat(const __FlashStringHelper * str) +{ + if (!str) return 0; + int length = strlen((const char *) str); + if (length == 0) return 1; + unsigned int newlen = len + length; + if (!reserve(newlen)) return 0; + strcpy(buffer + len, (const char *) str); + len = newlen; + return 1; +} + +/*********************************************/ +/* Concatenate */ +/*********************************************/ + +StringSumHelper & operator + (const StringSumHelper &lhs, const String &rhs) +{ + StringSumHelper &a = const_cast<StringSumHelper&>(lhs); + if (!a.concat(rhs.buffer, rhs.len)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, const char *cstr) +{ + StringSumHelper &a = const_cast<StringSumHelper&>(lhs); + if (!cstr || !a.concat(cstr, strlen(cstr))) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, char c) +{ + StringSumHelper &a = const_cast<StringSumHelper&>(lhs); + if (!a.concat(c)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, unsigned char num) +{ + StringSumHelper &a = const_cast<StringSumHelper&>(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, int num) +{ + StringSumHelper &a = const_cast<StringSumHelper&>(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, unsigned int num) +{ + StringSumHelper &a = const_cast<StringSumHelper&>(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, long num) +{ + StringSumHelper &a = const_cast<StringSumHelper&>(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, unsigned long num) +{ + StringSumHelper &a = const_cast<StringSumHelper&>(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, float num) +{ + StringSumHelper &a = const_cast<StringSumHelper&>(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, double num) +{ + StringSumHelper &a = const_cast<StringSumHelper&>(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs) +{ + StringSumHelper &a = const_cast<StringSumHelper&>(lhs); + if (!a.concat(rhs)) a.invalidate(); + return a; +} + +/*********************************************/ +/* Comparison */ +/*********************************************/ + +int String::compareTo(const String &s) const +{ + if (!buffer || !s.buffer) + { + if (s.buffer && s.len > 0) return 0 - *(unsigned char *)s.buffer; + if (buffer && len > 0) return *(unsigned char *)buffer; + return 0; + } + return strcmp(buffer, s.buffer); +} + +unsigned char String::equals(const String &s2) const +{ + return (len == s2.len && compareTo(s2) == 0); +} + +unsigned char String::equals(const char *cstr) const +{ + if (len == 0) return (cstr == NULL || *cstr == 0); + if (cstr == NULL) return buffer[0] == 0; + return strcmp(buffer, cstr) == 0; +} + +unsigned char String::operator<(const String &rhs) const +{ + return compareTo(rhs) < 0; +} + +unsigned char String::operator>(const String &rhs) const +{ + return compareTo(rhs) > 0; +} + +unsigned char String::operator<=(const String &rhs) const +{ + return compareTo(rhs) <= 0; +} + +unsigned char String::operator>=(const String &rhs) const +{ + return compareTo(rhs) >= 0; +} + +unsigned char String::equalsIgnoreCase( const String &s2 ) const +{ + if (this == &s2) return 1; + if (len != s2.len) return 0; + if (len == 0) return 1; + const char *p1 = buffer; + const char *p2 = s2.buffer; + while (*p1) + { + if (tolower(*p1++) != tolower(*p2++)) return 0; + } + return 1; +} + +unsigned char String::startsWith( const String &s2 ) const +{ + if (len < s2.len) return 0; + return startsWith(s2, 0); +} + +unsigned char String::startsWith( const String &s2, unsigned int offset ) const +{ + if (offset > len - s2.len || !buffer || !s2.buffer) return 0; + return strncmp( &buffer[offset], s2.buffer, s2.len ) == 0; +} + +unsigned char String::endsWith( const String &s2 ) const +{ + if ( len < s2.len || !buffer || !s2.buffer) return 0; + return strcmp(&buffer[len - s2.len], s2.buffer) == 0; +} + +/*********************************************/ +/* Character Access */ +/*********************************************/ + +char String::charAt(unsigned int loc) const +{ + return operator[](loc); +} + +void String::setCharAt(unsigned int loc, char c) +{ + if (loc < len) buffer[loc] = c; +} + +char & String::operator[](unsigned int index) +{ + static char dummy_writable_char; + if (index >= len || !buffer) + { + dummy_writable_char = 0; + return dummy_writable_char; + } + return buffer[index]; +} + +char String::operator[]( unsigned int index ) const +{ + if (index >= len || !buffer) return 0; + return buffer[index]; +} + +void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index) const +{ + if (!bufsize || !buf) return; + if (index >= len) + { + buf[0] = 0; + return; + } + unsigned int n = bufsize - 1; + if (n > len - index) n = len - index; + strncpy((char *)buf, buffer + index, n); + buf[n] = 0; +} + +/*********************************************/ +/* Search */ +/*********************************************/ + +int String::indexOf(char c) const +{ + return indexOf(c, 0); +} + +int String::indexOf( char ch, unsigned int fromIndex ) const +{ + if (fromIndex >= len) return -1; + const char* temp = strchr(buffer + fromIndex, ch); + if (temp == NULL) return -1; + return temp - buffer; +} + +int String::indexOf(const String &s2) const +{ + return indexOf(s2, 0); +} + +int String::indexOf(const String &s2, unsigned int fromIndex) const +{ + if (fromIndex >= len) return -1; + const char *found = strstr(buffer + fromIndex, s2.buffer); + if (found == NULL) return -1; + return found - buffer; +} + +int String::lastIndexOf( char theChar ) const +{ + return lastIndexOf(theChar, len - 1); +} + +int String::lastIndexOf(char ch, unsigned int fromIndex) const +{ + if (fromIndex >= len) return -1; + char tempchar = buffer[fromIndex + 1]; + buffer[fromIndex + 1] = '\0'; + char* temp = strrchr( buffer, ch ); + buffer[fromIndex + 1] = tempchar; + if (temp == NULL) return -1; + return temp - buffer; +} + +int String::lastIndexOf(const String &s2) const +{ + return lastIndexOf(s2, len - s2.len); +} + +int String::lastIndexOf(const String &s2, unsigned int fromIndex) const +{ + if (s2.len == 0 || len == 0 || s2.len > len) return -1; + if (fromIndex >= len) fromIndex = len - 1; + int found = -1; + for (char *p = buffer; p <= buffer + fromIndex; p++) + { + p = strstr(p, s2.buffer); + if (!p) break; + if ((unsigned int)(p - buffer) <= fromIndex) found = p - buffer; + } + return found; +} + +String String::substring(unsigned int left, unsigned int right) const +{ + if (left > right) + { + unsigned int temp = right; + right = left; + left = temp; + } + String out; + if (left >= len) return out; + if (right > len) right = len; + char temp = buffer[right]; // save the replaced character + buffer[right] = '\0'; + out = buffer + left; // pointer arithmetic + buffer[right] = temp; //restore character + return out; +} + +/*********************************************/ +/* Modification */ +/*********************************************/ + +void String::replace(char find, char replace) +{ + if (!buffer) return; + for (char *p = buffer; *p; p++) + { + if (*p == find) *p = replace; + } +} + +void String::replace(const String& find, const String& replace) +{ + if (len == 0 || find.len == 0) return; + int diff = replace.len - find.len; + char *readFrom = buffer; + char *foundAt; + if (diff == 0) + { + while ((foundAt = strstr(readFrom, find.buffer)) != NULL) + { + memcpy(foundAt, replace.buffer, replace.len); + readFrom = foundAt + replace.len; + } + } + else if (diff < 0) + { + char *writeTo = buffer; + while ((foundAt = strstr(readFrom, find.buffer)) != NULL) + { + unsigned int n = foundAt - readFrom; + memcpy(writeTo, readFrom, n); + writeTo += n; + memcpy(writeTo, replace.buffer, replace.len); + writeTo += replace.len; + readFrom = foundAt + find.len; + len += diff; + } + strcpy(writeTo, readFrom); + } + else + { + unsigned int size = len; // compute size needed for result + while ((foundAt = strstr(readFrom, find.buffer)) != NULL) + { + readFrom = foundAt + find.len; + size += diff; + } + if (size == len) return; + if (size > capacity && !changeBuffer(size)) return; // XXX: tell user! + int index = len - 1; + while (index >= 0 && (index = lastIndexOf(find, index)) >= 0) + { + readFrom = buffer + index + find.len; + memmove(readFrom + diff, readFrom, len - (readFrom - buffer)); + len += diff; + buffer[len] = 0; + memcpy(buffer + index, replace.buffer, replace.len); + index--; + } + } +} + +void String::remove(unsigned int index) +{ + // Pass the biggest integer as the count. The remove method + // below will take care of truncating it at the end of the + // string. + remove(index, (unsigned int) -1); +} + +void String::remove(unsigned int index, unsigned int count) +{ + if (index >= len || count == 0) + { + return; + } + if (count > len - index) + { + count = len - index; + } + char *writeTo = buffer + index; + memmove(writeTo, buffer + index + count, len - index - count); + len -= count; + buffer[len] = '\0'; +} + +void String::toLowerCase(void) +{ + if (!buffer) return; + for (char *p = buffer; *p; p++) + { + *p = tolower(*p); + } +} + +void String::toUpperCase(void) +{ + if (!buffer) return; + for (char *p = buffer; *p; p++) + { + *p = toupper(*p); + } +} + +void String::trim(void) +{ + if (!buffer || len == 0) return; + char *begin = buffer; + while (isspace(*begin)) begin++; + char *end = buffer + len - 1; + while (isspace(*end) && end >= begin) end--; + len = end + 1 - begin; + if (begin > buffer) memcpy(buffer, begin, len); + buffer[len] = 0; +} + +/*********************************************/ +/* Parsing / Conversion */ +/*********************************************/ + +long String::toInt(void) const +{ + if (buffer) return atol(buffer); + return 0; +} + +float String::toFloat(void) const +{ + if (buffer) return float(atof(buffer)); + return 0; +} + +extern "C" { +#include <stdio.h> +#include <stdarg.h> +} + +#ifdef SUPPORTS_WSTRING_SPRINTF + +#define SPRINTF_BUFFER_LENGTH 100 + +// Work in progress to support printf. +// Need to implement stream FILE to write individual chars to chosen serial port +int String::sprintf (const char *__restrict __format, ...) +{ + char printf_buff[SPRINTF_BUFFER_LENGTH]; + + va_list args; + va_start(args, __format); + int ret_status = vsnprintf(printf_buff, sizeof(printf_buff), __format, args); + va_end(args); + copy(printf_buff, strlen(printf_buff)); + + return ret_status; +} + +#endif diff --git a/x_track/src/App/Utils/WString/WString.h b/x_track/src/App/Utils/WString/WString.h new file mode 100644 index 0000000000000000000000000000000000000000..e01bd0e0c6b1bc9480e8c65ca72818dc0741b689 --- /dev/null +++ b/x_track/src/App/Utils/WString/WString.h @@ -0,0 +1,302 @@ +/* + WString.h - String library for Wiring & Arduino + ...mostly rewritten by Paul Stoffregen... + Copyright (c) 2009-10 Hernando Barragan. All right reserved. + Copyright 2011, Paul Stoffregen, paul@pjrc.com + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef String_class_h +#define String_class_h +#ifdef __cplusplus +extern "C"{ +#include "dtostrf.h" +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +} + +#define SUPPORTS_WSTRING_SPRINTF + +//#include <pgmspace.h> + +#define PGM_P const char * + +#define PGM_VOID_P const void * + +//#define PSTR(s) ((const PROGMEM char *)(s)) + + +// When compiling programs with this class, the following gcc parameters +// dramatically increase performance and memory (RAM) efficiency, typically +// with little or no increase in code size. +// -felide-constructors +// -std=c++0x + +class __FlashStringHelper; +#define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal))) + +// An inherited class for holding the result of a concatenation. These +// result objects are assumed to be writable by subsequent concatenations. +class StringSumHelper; + +// The string class +class String +{ + // use a function pointer to allow for "if (s)" without the + // complications of an operator bool(). for more information, see: + // http://www.artima.com/cppsource/safebool.html + typedef void (String::*StringIfHelperType)() const; + void StringIfHelper() const {} + +public: + // constructors + // creates a copy of the initial value. + // if the initial value is null or invalid, or if memory allocation + // fails, the string will be marked as invalid (i.e. "if (s)" will + // be false). + String(const char *cstr = ""); + String(const String &str); + String(const __FlashStringHelper *str); +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) + String(String &&rval); + String(StringSumHelper &&rval); +#endif + explicit String(char c); + explicit String(unsigned char, unsigned char base = 10); + explicit String(int, unsigned char base = 10); + explicit String(unsigned int, unsigned char base = 10); + explicit String(long, unsigned char base = 10); + explicit String(unsigned long, unsigned char base = 10); + explicit String(float, unsigned char decimalPlaces = 2); + explicit String(double, unsigned char decimalPlaces = 2); + ~String(void); + + // memory management + // return true on success, false on failure (in which case, the string + // is left unchanged). reserve(0), if successful, will validate an + // invalid string (i.e., "if (s)" will be true afterwards) + unsigned char reserve(unsigned int size); + inline unsigned int length(void) const { + return len; + } + + // creates a copy of the assigned value. if the value is null or + // invalid, or if the memory allocation fails, the string will be + // marked as invalid ("if (s)" will be false). + String & operator = (const String &rhs); + String & operator = (const char *cstr); + String & operator = (const __FlashStringHelper *str); +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) + String & operator = (String &&rval); + String & operator = (StringSumHelper &&rval); +#endif + + // concatenate (works w/ built-in types) + + // returns true on success, false on failure (in which case, the string + // is left unchanged). if the argument is null or invalid, the + // concatenation is considered unsucessful. + unsigned char concat(const String &str); + unsigned char concat(const char *cstr); + unsigned char concat(char c); + unsigned char concat(unsigned char c); + unsigned char concat(int num); + unsigned char concat(unsigned int num); + unsigned char concat(long num); + unsigned char concat(unsigned long num); + unsigned char concat(float num); + unsigned char concat(double num); + unsigned char concat(const __FlashStringHelper * str); + + // if there's not enough memory for the concatenated value, the string + // will be left unchanged (but this isn't signalled in any way) + String & operator += (const String &rhs) { + concat(rhs); + return (*this); + } + String & operator += (const char *cstr) { + concat(cstr); + return (*this); + } + String & operator += (char c) { + concat(c); + return (*this); + } + String & operator += (unsigned char num) { + concat(num); + return (*this); + } + String & operator += (int num) { + concat(num); + return (*this); + } + String & operator += (unsigned int num) { + concat(num); + return (*this); + } + String & operator += (long num) { + concat(num); + return (*this); + } + String & operator += (unsigned long num) { + concat(num); + return (*this); + } + String & operator += (float num) { + concat(num); + return (*this); + } + String & operator += (double num) { + concat(num); + return (*this); + } + String & operator += (const __FlashStringHelper *str) { + concat(str); + return (*this); + } + + friend StringSumHelper & operator + (const StringSumHelper &lhs, const String &rhs); + friend StringSumHelper & operator + (const StringSumHelper &lhs, const char *cstr); + friend StringSumHelper & operator + (const StringSumHelper &lhs, char c); + friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned char num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, int num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned int num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, long num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned long num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, float num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, double num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs); + + // comparison (only works w/ Strings and "strings") + operator StringIfHelperType() const { + return buffer ? &String::StringIfHelper : 0; + } + int compareTo(const String &s) const; + unsigned char equals(const String &s) const; + unsigned char equals(const char *cstr) const; + unsigned char operator == (const String &rhs) const { + return equals(rhs); + } + unsigned char operator == (const char *cstr) const { + return equals(cstr); + } + unsigned char operator != (const String &rhs) const { + return !equals(rhs); + } + unsigned char operator != (const char *cstr) const { + return !equals(cstr); + } + unsigned char operator < (const String &rhs) const; + unsigned char operator > (const String &rhs) const; + unsigned char operator <= (const String &rhs) const; + unsigned char operator >= (const String &rhs) const; + unsigned char equalsIgnoreCase(const String &s) const; + unsigned char startsWith( const String &prefix) const; + unsigned char startsWith(const String &prefix, unsigned int offset) const; + unsigned char endsWith(const String &suffix) const; + + // character acccess + char charAt(unsigned int index) const; + void setCharAt(unsigned int index, char c); + char operator [] (unsigned int index) const; + char& operator [] (unsigned int index); + void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index = 0) const; + void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const + { + getBytes((unsigned char *)buf, bufsize, index); + } + const char * c_str() const { + return buffer; + } + char* begin() { + return buffer; + } + char* end() { + return buffer + length(); + } + const char* begin() const { + return c_str(); + } + const char* end() const { + return c_str() + length(); + } + + // search + int indexOf( char ch ) const; + int indexOf( char ch, unsigned int fromIndex ) const; + int indexOf( const String &str ) const; + int indexOf( const String &str, unsigned int fromIndex ) const; + int lastIndexOf( char ch ) const; + int lastIndexOf( char ch, unsigned int fromIndex ) const; + int lastIndexOf( const String &str ) const; + int lastIndexOf( const String &str, unsigned int fromIndex ) const; + String substring( unsigned int beginIndex ) const { + return substring(beginIndex, len); + }; + String substring( unsigned int beginIndex, unsigned int endIndex ) const; + + // modification + void replace(char find, char replace); + void replace(const String& find, const String& replace); + void remove(unsigned int index); + void remove(unsigned int index, unsigned int count); + void toLowerCase(void); + void toUpperCase(void); + void trim(void); + + // parsing/conversion + long toInt(void) const; + float toFloat(void) const; + + //sprintf support + int sprintf(const char *__restrict __format, ...); + +protected: + char *buffer; // the actual char array + unsigned int capacity; // the array length minus one (for the '\0') + unsigned int len; // the String length (not counting the '\0') +protected: + void init(void); + void invalidate(void); + unsigned char changeBuffer(unsigned int maxStrLen); + unsigned char concat(const char *cstr, unsigned int length); + + // copy and move + String & copy(const char *cstr, unsigned int length); + String & copy(const __FlashStringHelper *pstr, unsigned int length); +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) + void move(String &rhs); +#endif +}; + +class StringSumHelper : public String +{ +public: + StringSumHelper(const String &s) : String(s) {} + StringSumHelper(const char *p) : String(p) {} + StringSumHelper(char c) : String(c) {} + StringSumHelper(unsigned char num) : String(num) {} + StringSumHelper(int num) : String(num) {} + StringSumHelper(unsigned int num) : String(num) {} + StringSumHelper(long num) : String(num) {} + StringSumHelper(unsigned long num) : String(num) {} + StringSumHelper(float num) : String(num) {} + StringSumHelper(double num) : String(num) {} +}; + +#endif // __cplusplus +#endif // String_class_h diff --git a/x_track/src/App/Utils/WString/dtostrf.c b/x_track/src/App/Utils/WString/dtostrf.c new file mode 100644 index 0000000000000000000000000000000000000000..7625ac0c7d48e30a66e01f0cdec86d2d6e64c5f6 --- /dev/null +++ b/x_track/src/App/Utils/WString/dtostrf.c @@ -0,0 +1,29 @@ +/* + dtostrf - Emulation for dtostrf function from avr-libc + Copyright (c) 2013 Arduino. All rights reserved. + Written by Cristian Maglie <c.maglie@bug.st> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "dtostrf.h" + +char *dtostrf (double val, signed char width, unsigned char prec, char *sout) { + char fmt[20]; + sprintf(fmt, "%%%d.%df", width, prec); + sprintf(sout, fmt, val); + return sout; +} + diff --git a/x_track/src/App/Utils/WString/dtostrf.h b/x_track/src/App/Utils/WString/dtostrf.h new file mode 100644 index 0000000000000000000000000000000000000000..ea8972aa4888098b8909f4df4e915244b5145b81 --- /dev/null +++ b/x_track/src/App/Utils/WString/dtostrf.h @@ -0,0 +1,34 @@ +/* + dtostrf - Emulation for dtostrf function from avr-libc + Copyright (c) 2013 Arduino. All rights reserved. + Written by Cristian Maglie <c.maglie@bug.st> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __DTOSTRF_H +#define __DTOSTRF_H +#include "stdio.h" +#ifdef __cplusplus +extern "C" { +#endif + +char *dtostrf (double val, signed char width, unsigned char prec, char *sout); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/x_track/src/App/Utils/WString/itoa.c b/x_track/src/App/Utils/WString/itoa.c new file mode 100755 index 0000000000000000000000000000000000000000..842a977143379ec787179516351695750c72929c --- /dev/null +++ b/x_track/src/App/Utils/WString/itoa.c @@ -0,0 +1,121 @@ +/* + Copyright (c) 2011 Arduino. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "itoa.h" + +#ifndef NULL +#define NULL ((void*)0) +#endif + +char *itoa(int value, char *string, int radix) +{ + return ltoa(value, string, radix) ; +} + +char *ltoa(long value, char *string, int radix) +{ + char tmp[33]; + char *tp = tmp; + long i; + unsigned long v; + int sign; + char *sp; + + if (string == NULL) + { + return 0 ; + } + + if (radix > 36 || radix <= 1) + { + return 0 ; + } + + sign = (radix == 10 && value < 0); + if (sign) + { + v = -value; + } + else + { + v = (unsigned long)value; + } + + while (v || tp == tmp) + { + i = v % radix; + v = v / radix; + if (i < 10) + *tp++ = i + '0'; + else + *tp++ = i + 'a' - 10; + } + + sp = string; + + if (sign) + *sp++ = '-'; + while (tp > tmp) + *sp++ = *--tp; + *sp = 0; + + return string; +} + +char *utoa(unsigned int value, char *string, int radix) +{ + return ultoa( value, string, radix ) ; +} + +char *ultoa(unsigned long value, char *string, int radix) +{ + char tmp[33]; + char *tp = tmp; + long i; + unsigned long v = value; + char *sp; + + if (string == NULL) + { + return 0; + } + + if (radix > 36 || radix <= 1) + { + return 0; + } + + while (v || tp == tmp) + { + i = v % radix; + v = v / radix; + if (i < 10) + *tp++ = i + '0'; + else + *tp++ = i + 'a' - 10; + } + + sp = string; + + + while (tp > tmp) + *sp++ = *--tp; + *sp = 0; + + return string; +} diff --git a/x_track/src/App/Utils/WString/itoa.h b/x_track/src/App/Utils/WString/itoa.h new file mode 100755 index 0000000000000000000000000000000000000000..2e606c030b4dcc14db972138c08bfc862276e470 --- /dev/null +++ b/x_track/src/App/Utils/WString/itoa.h @@ -0,0 +1,35 @@ +/* + Copyright (c) 2011 Arduino. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _ITOA_ +#define _ITOA_ + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +char *itoa(int value, char *string, int radix); +char *ltoa(long value, char *string, int radix); +char *utoa(unsigned int value, char *string, int radix); +char *ultoa(unsigned long value, char *string, int radix); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif // _ITOA_ diff --git a/x_track/src/App/Utils/easing/easing.c b/x_track/src/App/Utils/easing/easing.c new file mode 100644 index 0000000000000000000000000000000000000000..ea99ac135aaaa28029ba4770666ff5cb6fffe425 --- /dev/null +++ b/x_track/src/App/Utils/easing/easing.c @@ -0,0 +1,331 @@ +/** + * @file easing.c + * @author uYanki (https://github.com/uYanki) + * @brief https://github.com/uYanki/board-stm32f103rc-berial/blob/main/7.Example/HAL/19.GUI/03%20u8g2/02%20menu/Lib/easing/easing.c + * @version 0.1 + * @date 2023-01-15 + * + * @copyright Copyright (c) 2023 + * + */ + +#include "easing.h" +#include <string.h> + +#define PI 3.1415926f + +static easing_tick_cb_t g_easing_tick_cb = 0; + +#define easing_mills() g_easing_tick_cb() + +static const float DH = 1.0f / 22.0f; +static const float D1 = 1.0f / 11.0f; +static const float D2 = 2.0f / 11.0f; +static const float D3 = 3.0f / 11.0f; +// static const float D4 = 4.0f / 11.0f; +static const float D5 = 5.0f / 11.0f; +static const float D7 = 7.0f / 11.0f; +static const float IH = 1.0f / (1.0f / 22.0f); +static const float I1 = 1.0f / (1.0f / 11.0f); +static const float I2 = 1.0f / (2.0f / 11.0f); +static const float I4D = 1.0f / (4.0f / 11.0f) / (4.0f / 11.0f); +// static const float IH = 1.0f / DH; +// static const float I1 = 1.0f / D1; +// static const float I2 = 1.0f / D2; +// static const float I4D = 1.0f / D4 / D4; + +float _easing_calc_InBounce(const float t) +{ + float s; + if (t < D1) { + s = t - DH; + s = DH - s * s * IH; + } else if (t < D3) { + s = t - D2; + s = D1 - s * s * I1; + } else if (t < D7) { + s = t - D5; + s = D2 - s * s * I2; + } else { + s = t - 1; + s = 1 - s * s * I4D; + } + return s; +} + +float _easing_calc_OutBounce(const float t) +{ + return 1.0f - _easing_calc_InBounce(1.0f - t); +} + +float _easing_calc_InOutBounce(const float t) +{ + return (t < 0.5f) ? _easing_calc_InBounce(t * 2.0f) * 0.5f : 1 - _easing_calc_InBounce(2.0f - t * 2.0f) * 0.5f; +} + +float _easing_calc_InCirc(const float t) +{ + return 1.0f - sqrtf(1.0f - t * t); +} + +float _easing_calc_OutCirc(const float t) +{ + return 1.0f - _easing_calc_InCirc(1.0f - t); +} + +float _easing_calc_InOutCirc(const float t) +{ + return (t < 0.5f) ? _easing_calc_InCirc(t * 2.0f) * 0.5f : 1 - _easing_calc_InCirc(2.0f - t * 2.0f) * 0.5f; +} + +float _easing_calc_InCubic(const float t) +{ + return t * t * t; +} + +float _easing_calc_OutCubic(const float t) +{ + return 1.0f - _easing_calc_InCubic(1.0f - t); +} + +float _easing_calc_InOutCubic(const float t) +{ + return (t < 0.5f) ? _easing_calc_InCubic(t * 2.0f) * 0.5f : 1 - _easing_calc_InCubic(2.0f - t * 2.0f) * 0.5f; +} + +float _easing_calc_OutElastic(const float t) +{ + float s = 1 - t; + return 1 - powf(s, 8) + sinf(t * t * 6 * PI) * s * s; +} + +float _easing_calc_InElastic(const float t) +{ + return 1.0f - _easing_calc_OutElastic(1.0f - t); +} + +float _easing_calc_InOutElastic(const float t) +{ + return (t < 0.5f) ? _easing_calc_InElastic(t * 2.0f) * 0.5f : 1 - _easing_calc_InElastic(2.0f - t * 2.0f) * 0.5f; +} + +float _easing_calc_InExpo(const float t) +{ + return powf(2, 10 * (t - 1)); +} + +float _easing_calc_OutExpo(const float t) +{ + return 1.0f - powf(2, -10 * t); +} + +float _easing_calc_InOutExpo(const float t) +{ + return (t < 0.5f) ? _easing_calc_InExpo(t * 2.0f) * 0.5f : 1 - _easing_calc_InExpo(2.0f - t * 2.0f) * 0.5f; +} + +float _easing_calc_Linear(const float t) +{ + return t; +} + +float _easing_calc_InQuad(const float t) +{ + return t * t; +} + +float _easing_calc_OutQuad(const float t) +{ + return 1.0f - _easing_calc_InQuad(1.0f - t); +} + +float _easing_calc_InOutQuad(const float t) +{ + return (t < 0.5f) ? _easing_calc_InQuad(t * 2.0f) * 0.5f : 1 - _easing_calc_InQuad(2.0f - t * 2.0f) * 0.5f; +} + +float _easing_calc_InQuart(const float t) +{ + return t * t * t * t; +} + +float _easing_calc_OutQuart(const float t) +{ + return 1.0f - _easing_calc_InQuart(1.0f - t); +} + +float _easing_calc_InOutQuart(const float t) +{ + return (t < 0.5f) ? _easing_calc_InQuart(t * 2.0f) * 0.5f : 1 - _easing_calc_InQuart(2.0f - t * 2.0f) * 0.5f; +} + +float _easing_calc_InQuint(const float t) +{ + return t * t * t * t * t; +} + +float _easing_calc_OutQuint(const float t) +{ + return 1.0f - _easing_calc_InQuint(1.0f - t); +} + +float _easing_calc_InOutQuint(const float t) +{ + return (t < 0.5f) ? _easing_calc_InQuint(t * 2.0f) * 0.5f : 1 - _easing_calc_InQuint(2.0f - t * 2.0f) * 0.5f; +} + +float _easing_calc_InSine(const float t) +{ + return 1.0f - cosf(t * (PI / 2)); +} + +float _easing_calc_OutSine(const float t) +{ + return 1.0f - _easing_calc_InSine(1.0f - t); +} + +float _easing_calc_InOutSine(const float t) +{ + return (t < 0.5f) ? _easing_calc_InSine(t * 2.0f) * 0.5f : 1 - _easing_calc_InSine(2.0f - t * 2.0f) * 0.5f; +} + +float _easing_calc_InBack(const float t) +{ + return 3 * t * t * t - 2 * t * t; +} + +float _easing_calc_OutBack(const float t) +{ + return 1.0f - _easing_calc_InBack(1.0f - t); +} + +float _easing_calc_InOutBack(const float t) +{ + return (t < 0.5f) ? _easing_calc_InBack(t * 2.0f) * 0.5f : 1 - _easing_calc_InBack(2.0f - t * 2.0f) * 0.5f; +} + +//////////////////////////////////////////////////////////////////////////////////// + +void easing_set_tick_callback(easing_tick_cb_t tick_cb) +{ + g_easing_tick_cb = tick_cb; +} + +void easing_init( + easing_t* pEasing, + easing_mode_t dwMode, + easing_calc_t lpfnCalc, + easing_pos_t nOffset, + uint16_t nFrameCount, + uint16_t nInterval) +{ + memset(pEasing, 0, sizeof(easing_t)); + pEasing->dwMode = dwMode; + pEasing->lpfnCalc = lpfnCalc == 0 ? _easing_calc_Linear : lpfnCalc; + pEasing->nOffset = nOffset; + pEasing->nFrameCount = (nFrameCount < 2) ? 2 : nFrameCount; + pEasing->nInterval = nInterval; + pEasing->bDirection = dwMode & EASING_DIR_REVERSE; +} + +void easing_start_absolute( + easing_t* pEasing, + easing_pos_t nStart, + easing_pos_t nStop) +{ + pEasing->nStart = nStart; + pEasing->nStop = nStop; + pEasing->nDelta = nStop - nStart; + + pEasing->nFrameIndex = 0; // first frame is nStart + pEasing->fProgress = 0.0f; + + pEasing->bDirection = pEasing->dwMode & EASING_DIR_REVERSE; + + if (pEasing->dwMode & EASING_TIMES_INFINITE) { + pEasing->nTimes = -1; + } else { + pEasing->nTimes = (pEasing->dwMode & EASING_TIMES_MANYTIMES) ? (pEasing->dwMode >> EASING_TIMES_SET) : 1; + if (pEasing->dwMode & EASING_DIR_BACKANDFORTH) + pEasing->nTimes *= 2; + } + +#ifdef easing_mills + pEasing->nMills = easing_mills(); +#endif +} + +void easing_start_relative( + easing_t* pEasing, + easing_pos_t nDistance) +{ + easing_start_absolute( + pEasing, +#if 1 + pEasing->nCurrent, // from current pos +#else + easing->nStop, // from stop pos +#endif + pEasing->nStop + nDistance); +} + +void easing_update(easing_t* pEasing) +{ + // isok + if (pEasing->nTimes == 0) + return; + +#ifdef easing_mills + if (pEasing->nInterval > 0) { + if (easing_mills() < pEasing->nMills) + return; + pEasing->nMills = easing_mills() + pEasing->nInterval; + } +#endif + + // next frame + ++pEasing->nFrameIndex; + + if (pEasing->nFrameIndex > pEasing->nFrameCount) { + if (pEasing->dwMode & EASING_DIR_BACKANDFORTH) { + // reverse direction + pEasing->bDirection = !pEasing->bDirection; + // skip once nStart/nStop pos + pEasing->nFrameIndex = 2; + } else { + // at first frame + pEasing->nFrameIndex = 1; + } + } + + if (pEasing->nFrameIndex == pEasing->nFrameCount) { + // at last frame + pEasing->fProgress = 1.0f; + pEasing->nCurrent = pEasing->bDirection ? pEasing->nStart : pEasing->nStop; + // decrease times + if (!(pEasing->dwMode & EASING_TIMES_INFINITE)) + if (--pEasing->nTimes) + return; + } else { + // calculate progress + pEasing->fProgress = (float)(pEasing->nFrameIndex - 1) / (pEasing->nFrameCount - 1); + // calculate position + pEasing->nCurrent = pEasing->bDirection ? (pEasing->nStop - pEasing->nDelta * pEasing->lpfnCalc(pEasing->fProgress)) : (pEasing->nStart + pEasing->nDelta * pEasing->lpfnCalc(pEasing->fProgress)); + } +} + +bool easing_isok(easing_t* pEasing) +{ + return pEasing->nTimes == 0; +} + +void easing_stop(easing_t* pEasing, easing_pos_t nCurrent) +{ + pEasing->nTimes = 0; + pEasing->nCurrent = nCurrent; +} + +easing_pos_t easing_curpos(easing_t* pEasing) +{ + return pEasing->nCurrent + pEasing->nOffset; +} diff --git a/x_track/src/App/Utils/easing/easing.h b/x_track/src/App/Utils/easing/easing.h new file mode 100644 index 0000000000000000000000000000000000000000..6771be5a0af0af254c5876e328c067f492101821 --- /dev/null +++ b/x_track/src/App/Utils/easing/easing.h @@ -0,0 +1,156 @@ +/** + * @file easing.h + * @author uYanki (https://github.com/uYanki) + * @brief https://github.com/uYanki/board-stm32f103rc-berial/blob/main/7.Example/HAL/19.GUI/03%20u8g2/02%20menu/Lib/easing/easing.h + * @version 0.1 + * @date 2023-01-15 + * + * @copyright Copyright (c) 2023 + * + */ + +// https://blog.csdn.net/z2014z/article/details/120691794 + +#ifndef __EASING_H__ +#define __EASING_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <math.h> +#include <stdbool.h> +#include <stdint.h> + +//////////////// + +typedef float (*easing_calc_t)(const float t); +typedef uint32_t (*easing_tick_cb_t)(void); + +typedef float easing_pos_t; // data type + +typedef enum { + EASING_MODE_BITCNT = 4, + EASING_MODE_MASK = (1 << EASING_MODE_BITCNT) - 1, + + EASING_TIMES_SINGLE = 0 << 0, // 单次(default) + EASING_TIMES_MANYTIMES = 1 << 0, // 多次 + EASING_TIMES_INFINITE = 1 << 1, // 循环 + + EASING_TIMES_SET = EASING_MODE_BITCNT, + + EASING_DIR_FORWARD = 0 << 0, // 正向(default) + EASING_DIR_REVERSE = 1 << 2, // 反向 + EASING_DIR_BACKANDFORTH = 1 << 3, // 往返 + +} easing_mode_t; + +#define EASING_MODE_DEFAULT ((easing_mode_t)(EASING_TIMES_SINGLE | EASING_DIR_FORWARD)) +#define EASING_MODE_NTIMES(n) EASING_TIMES_MANYTIMES | (n << EASING_TIMES_SET) + +#define EASING_INTERVAL_NONE 0 + +typedef struct easing { + easing_mode_t dwMode; + + // type + easing_calc_t lpfnCalc; // _easing_calc_xxx + + // position + easing_pos_t nStart; + easing_pos_t nStop; + easing_pos_t nOffset; + easing_pos_t nDelta; + easing_pos_t nCurrent; // range: [nStart, nStop] + + // progress + uint16_t nFrameCount; // range: [2,n], 1: nStart/nStop; n:nStop/nStart; + uint16_t nFrameIndex; // current frame. range: [0, nFrameCount], 0:nStart/nStop; nFrameCount:nStop/nStart; + float fProgress; // current progress. range: [0,1] + + int16_t nTimes; + bool bDirection; // true: reverse, false:forward + + uint32_t nMills; + uint16_t nInterval; // minimum time interval per frame (ms) +} easing_t; + +//////////////// _easing_calc_xxx + +float _easing_calc_Linear(const float t); // linear t + +float _easing_calc_InQuad(const float t); // quadratic t^2 +float _easing_calc_OutQuad(const float t); +float _easing_calc_InOutQuad(const float t); + +float _easing_calc_InCubic(const float t); // cubic t^3 +float _easing_calc_OutCubic(const float t); +float _easing_calc_InOutCubic(const float t); + +float _easing_calc_InQuart(const float t); // quartic t^4 +float _easing_calc_OutQuart(const float t); +float _easing_calc_InOutQuart(const float t); + +float _easing_calc_InQuint(const float t); // quintic t^5 +float _easing_calc_OutQuint(const float t); +float _easing_calc_InOutQuint(const float t); + +float _easing_calc_InSine(const float t); // sinusoidal 正弦 sin(t) +float _easing_calc_OutSine(const float t); +float _easing_calc_InOutSine(const float t); + +float _easing_calc_InExpo(const float t); // exponential 指数 2^t +float _easing_calc_OutExpo(const float t); +float _easing_calc_InOutExpo(const float t); + +float _easing_calc_InCirc(const float t); // circular 圆形 +float _easing_calc_OutCirc(const float t); +float _easing_calc_InOutCirc(const float t); + +float _easing_calc_InBack(const float t); // elastic 衰减三次幂 (s+1)t^3 - st^2 +float _easing_calc_OutBack(const float t); +float _easing_calc_InOutBack(const float t); + +float _easing_calc_InElastic(const float t); // elastic 衰减正弦 +float _easing_calc_OutElastic(const float t); +float _easing_calc_InOutElastic(const float t); + +float _easing_calc_InBounce(const float t); // back 衰减反弹 +float _easing_calc_OutBounce(const float t); +float _easing_calc_InOutBounce(const float t); + +//////////////// + +void easing_set_tick_callback(easing_tick_cb_t tick_cb); + +void easing_init( + easing_t* pEasing, + easing_mode_t dwMode, // default: EASING_TIMES_SINGLE | EASING_DIR_FORWARD + easing_calc_t lpfnCalc, // default: _easing_calc_Linear + easing_pos_t nOffset, // default: 0 + uint16_t nFrameCount, // default: 2 + uint16_t nInterval // default: EASING_INTERVAL_NONE +); + +void easing_start_absolute( + easing_t* pEasing, + easing_pos_t nStart, + easing_pos_t nStop); + +void easing_start_relative( + easing_t* pEasing, + easing_pos_t nDistance); + +// update easing position +void easing_update(easing_t* easing); + +bool easing_isok(easing_t* pEasing); +void easing_stop(easing_t* pEasing, easing_pos_t nCurrent); + +easing_pos_t easing_curpos(easing_t* pEasing); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/x_track/src/App/Utils/easing/easing_demo.c b/x_track/src/App/Utils/easing/easing_demo.c new file mode 100644 index 0000000000000000000000000000000000000000..dfc18a6c92c14bf20ca48a768e555a2182fcd2e4 --- /dev/null +++ b/x_track/src/App/Utils/easing/easing_demo.c @@ -0,0 +1,47 @@ +/** + * @file easing_demo.c + * @author uYanki (https://github.com/uYanki) + * @brief easing demo + * @version 0.1 + * @date 2023-01-15 + * + * @copyright Copyright (c) 2023 + * + */ + +#if 0 + +#include "easing.h" +#include <stdio.h> + +void easing_demo(void) +{ +#if 0 + // easing_t e = easing_create(EASING_DIR_FORWARD | EASING_TIMES_SINGLE, _easing_calc_Linear, 10, 5, 0); + // easing_t e = easing_create(EASING_DIR_FORWARD | EASING_TIMES_MANYTIMES | (2 << EASING_TIMES_SET), _easing_calc_Linear, 0, 5, 0); + // easing_t e = easing_create(EASING_DIR_REVERSE | EASING_TIMES_MANYTIMES | (2 << EASING_TIMES_SET), _easing_calc_Linear, 0, 1, 0); + easing_t e = easing_create(EASING_DIR_BACKANDFORTH | EASING_TIMES_MANYTIMES | (2 << EASING_TIMES_SET), _easing_calc_Linear, 0, 5, 0); + easing_start_relative(&e, 10); // 0 [2.5 5 7.5] 10 + while (!easing_isok(&e)) { + easing_update(&e); + printf("%f\r\n", easing_curpos(&e)); + // printf("%f\r\n", e.nCurrent); + } +#endif + + easing_t e = easing_create(EASING_MODE_DEFAULT, _easing_calc_Linear, 0, 400, 0); + + easing_start_absolute(&e, -1.57f, 1.57f); + + char buff[32]; + FILE* f = fopen("data.csv", "w"); + while (!easing_isok(&e)) { + easing_update(&e); + float x = easing_curpos(&e); + snprintf(buff, sizeof(buff),"%f\n", x); + fputs(buff, f); + } + fclose(f); +} + +#endif diff --git a/x_track/src/App/Utils/easing/type.png b/x_track/src/App/Utils/easing/type.png new file mode 100755 index 0000000000000000000000000000000000000000..dee6870a785902448cf9e9254eff6abc23bc7ca6 Binary files /dev/null and b/x_track/src/App/Utils/easing/type.png differ diff --git a/x_track/src/App/Utils/lv_anim_label/lv_anim_label.c b/x_track/src/App/Utils/lv_anim_label/lv_anim_label.c new file mode 100644 index 0000000000000000000000000000000000000000..1fe04fcec58009eb473a491f785a724a20f193ad --- /dev/null +++ b/x_track/src/App/Utils/lv_anim_label/lv_anim_label.c @@ -0,0 +1,368 @@ +/* + * MIT License + * Copyright (c) 2021 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "lv_anim_label.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_anim_label_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void lv_anim_label_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_anim_label_set_x(void* obj, int32_t x); +static void lv_anim_label_set_y(void* obj, int32_t y); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_anim_label_class = +{ + .constructor_cb = lv_anim_label_constructor, + .instance_size = sizeof(lv_anim_label_t), + .base_class = &lv_obj_class +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_anim_label_create(lv_obj_t * parent) +{ + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE); + return obj; +} + +void lv_anim_label_set_dir(lv_obj_t * obj, lv_dir_t dir) +{ + lv_anim_label_set_enter_dir(obj, dir); + lv_anim_label_set_exit_dir(obj, dir); +} + +void lv_anim_label_set_enter_dir(lv_obj_t * obj, lv_dir_t dir) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t * alabel = (lv_anim_label_t *)obj; + + alabel->enter_dir = dir; +} + +void lv_anim_label_set_exit_dir(lv_obj_t * obj, lv_dir_t dir) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t * alabel = (lv_anim_label_t *)obj; + + alabel->exit_dir = dir; +} + +void lv_anim_label_set_time(lv_obj_t * obj, uint32_t duration) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t * alabel = (lv_anim_label_t *)obj; + + alabel->duration = duration; +} + +void lv_anim_label_set_path(lv_obj_t * obj, lv_anim_path_cb_t path_cb) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t * alabel = (lv_anim_label_t *)obj; + + alabel->path_cb = path_cb; +} + +void lv_anim_label_add_style(lv_obj_t * obj, lv_style_t * style) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t * alabel = (lv_anim_label_t *)obj; + + lv_obj_add_style(alabel->label_1, style, LV_PART_MAIN); + lv_obj_add_style(alabel->label_2, style, LV_PART_MAIN); +} + +void lv_anim_label_set_custom_enter_anim(lv_obj_t * obj, const lv_anim_t * a) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t * alabel = (lv_anim_label_t *)obj; + + if (a != NULL) + { + alabel->a_enter = *a; + } +} + +void lv_anim_label_set_custom_exit_anim(lv_obj_t * obj, const lv_anim_t * a) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t * alabel = (lv_anim_label_t *)obj; + + if (a != NULL) + { + alabel->a_exit = *a; + } +} + +static void lv_anim_label_start_anim( + lv_obj_t * label, + lv_dir_t dir, + int32_t start, + int32_t end, + uint32_t duration, + lv_anim_path_cb_t path_cb +) +{ + lv_anim_exec_xcb_t exec_xcb; + + if (dir & LV_DIR_HOR) + { + exec_xcb = lv_anim_label_set_x; + } + else if (dir & LV_DIR_VER) + { + exec_xcb = lv_anim_label_set_y; + } + else + { + return; + } + + lv_anim_t a; + lv_anim_init(&a); + lv_anim_set_var(&a, label); + lv_anim_set_values(&a, start, end); + lv_anim_set_time(&a, duration); + lv_anim_set_path_cb(&a, path_cb); + lv_anim_set_exec_cb(&a, exec_xcb); + lv_anim_start(&a); +} + +void lv_anim_label_push_text(lv_obj_t * obj, const char * txt) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t * alabel = (lv_anim_label_t *)obj; + + lv_obj_t * label_exit = alabel->label_act; + lv_obj_t * label_enter = alabel->label_act == alabel->label_1 ? alabel->label_2 : alabel->label_1; + lv_dir_t enter_dir = alabel->enter_dir; + lv_dir_t exit_dir = alabel->exit_dir; + + lv_label_set_text(label_enter, txt); + lv_obj_update_layout(label_enter); + + lv_coord_t obj_width = lv_obj_get_width(obj); + lv_coord_t obj_height = lv_obj_get_height(obj); + + /* enter */ + lv_coord_t label_enter_width = lv_obj_get_width(label_enter); + lv_coord_t label_enter_height = lv_obj_get_height(label_enter); + lv_coord_t label_enter_end_x = (obj_width - label_enter_width) / 2; + lv_coord_t label_enter_end_y = (obj_height - label_enter_height) / 2; + + if (enter_dir & LV_DIR_HOR) + { + lv_obj_set_y(label_enter, label_enter_end_y); + lv_coord_t start_x = label_enter_end_x; + start_x += ((enter_dir == LV_DIR_LEFT) ? -obj_width : obj_width); + lv_anim_label_start_anim( + label_enter, + enter_dir, + start_x, + label_enter_end_x, + alabel->duration, + alabel->path_cb + ); + } + else if (enter_dir & LV_DIR_VER) + { + lv_obj_set_x(label_enter, label_enter_end_x); + lv_coord_t start_y = label_enter_end_y; + start_y += ((enter_dir == LV_DIR_TOP) ? -obj_height : obj_height); + lv_anim_label_start_anim( + label_enter, + enter_dir, + start_y, + label_enter_end_y, + alabel->duration, + alabel->path_cb + ); + } + else + { + lv_obj_set_pos(label_enter, label_enter_end_x, label_enter_end_y); + } + + if (alabel->a_enter.exec_cb != NULL) + { + lv_anim_set_var(&alabel->a_enter, label_enter); + lv_anim_start(&alabel->a_enter); + } + + /* exit */ + lv_coord_t label_exit_width = lv_obj_get_width(label_exit); + lv_coord_t label_exit_height = lv_obj_get_height(label_exit); + lv_coord_t label_exit_start_x = (obj_width - label_exit_width) / 2; + lv_coord_t label_exit_start_y = (obj_height - label_exit_height) / 2; + + if (exit_dir & LV_DIR_HOR) + { + lv_obj_set_y(label_exit, label_exit_start_y); + lv_coord_t end_x = label_exit_start_x; + end_x += ((exit_dir == LV_DIR_LEFT) ? obj_width : -obj_width); + lv_anim_label_start_anim( + label_exit, + exit_dir, + label_exit_start_x, + end_x, + alabel->duration, + alabel->path_cb + ); + } + else if (exit_dir & LV_DIR_VER) + { + lv_obj_set_x(label_exit, label_exit_start_x); + lv_coord_t end_y = label_exit_start_y; + end_y += ((exit_dir == LV_DIR_TOP) ? obj_height : -obj_height); + lv_anim_label_start_anim( + label_exit, + exit_dir, + label_exit_start_y, + end_y, + alabel->duration, + alabel->path_cb + ); + } + else + { + lv_obj_set_pos(label_exit, label_exit_start_x, label_exit_start_y); + } + + if (alabel->a_exit.exec_cb != NULL) + { + lv_anim_set_var(&alabel->a_exit, label_exit); + lv_anim_start(&alabel->a_exit); + } + + alabel->label_act = label_enter; +} + +lv_dir_t lv_anim_label_get_enter_dir(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t * alabel = (lv_anim_label_t *)obj; + + return alabel->enter_dir; +} + +lv_dir_t lv_anim_label_get_exit_dir(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t * alabel = (lv_anim_label_t *)obj; + + return alabel->exit_dir; +} + +uint32_t lv_anim_label_get_time(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t * alabel = (lv_anim_label_t *)obj; + + return alabel->duration; +} + +lv_anim_path_cb_t lv_anim_label_get_path(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t * alabel = (lv_anim_label_t *)obj; + + return alabel->path_cb; +} + +const char * lv_anim_label_get_text(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_anim_label_t * alabel = (lv_anim_label_t *)obj; + + return lv_label_get_text(alabel->label_act); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_anim_label_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_TRACE_OBJ_CREATE("begin"); + + lv_anim_label_t * alabel = (lv_anim_label_t *)obj; + + alabel->label_1 = lv_label_create(obj); + lv_label_set_text(alabel->label_1, ""); + + alabel->label_2 = lv_label_create(obj); + lv_label_set_text(alabel->label_2, ""); + + alabel->label_act = alabel->label_1; + //alabel->dir = LV_DIR_BOTTOM; + alabel->duration = 500; + alabel->path_cb = lv_anim_path_ease_out; + + lv_anim_init(&alabel->a_enter); + lv_anim_init(&alabel->a_exit); + + LV_TRACE_OBJ_CREATE("finished"); +} + +static void lv_anim_label_set_x(void * obj, int32_t x) +{ + lv_obj_set_x(obj, x); +} + +static void lv_anim_label_set_y(void * obj, int32_t y) +{ + lv_obj_set_y(obj, y); +} diff --git a/x_track/src/App/Utils/lv_anim_label/lv_anim_label.h b/x_track/src/App/Utils/lv_anim_label/lv_anim_label.h new file mode 100644 index 0000000000000000000000000000000000000000..0fbe668ede4982e2c212afe31997746b3204515f --- /dev/null +++ b/x_track/src/App/Utils/lv_anim_label/lv_anim_label.h @@ -0,0 +1,68 @@ +/** + * @file lv_anim_label.h + * + */ + +#ifndef LV_ANIM_LABEL_H +#define LV_ANIM_LABEL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "lvgl/lvgl.h" +#include "lvgl/src/lvgl_private.h" + +typedef struct { + lv_obj_t obj; + lv_obj_t * label_1; + lv_obj_t * label_2; + lv_obj_t * label_act; + lv_dir_t enter_dir; + lv_dir_t exit_dir; + lv_anim_t a_enter; + lv_anim_t a_exit; + uint32_t duration; + lv_anim_path_cb_t path_cb; +}lv_anim_label_t; + +extern const lv_obj_class_t lv_anim_label_class; + +lv_obj_t * lv_anim_label_create(lv_obj_t * parent); + +void lv_anim_label_set_dir(lv_obj_t * obj, lv_dir_t dir); + +void lv_anim_label_set_enter_dir(lv_obj_t * obj, lv_dir_t dir); + +void lv_anim_label_set_exit_dir(lv_obj_t * obj, lv_dir_t dir); + +void lv_anim_label_set_time(lv_obj_t * obj, uint32_t duration); + +void lv_anim_label_set_path(lv_obj_t * obj, lv_anim_path_cb_t path_cb); + +void lv_anim_label_add_style(lv_obj_t * obj, lv_style_t * style); + +void lv_anim_label_set_custom_enter_anim(lv_obj_t * obj, const lv_anim_t * a); + +void lv_anim_label_set_custom_exit_anim(lv_obj_t * obj, const lv_anim_t * a); + +void lv_anim_label_push_text(lv_obj_t * obj, const char* txt); + +lv_dir_t lv_anim_label_get_enter_dir(lv_obj_t * obj); + +lv_dir_t lv_anim_label_get_exit_dir(lv_obj_t * obj); + +uint32_t lv_anim_label_get_time(lv_obj_t * obj); + +lv_anim_path_cb_t lv_anim_label_get_path(lv_obj_t * obj); + +const char * lv_anim_label_get_text(lv_obj_t * obj); + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_ANIM_LABEL_H*/ diff --git a/x_track/src/App/Utils/lv_ext/lv_anim_timeline_wrapper.c b/x_track/src/App/Utils/lv_ext/lv_anim_timeline_wrapper.c new file mode 100644 index 0000000000000000000000000000000000000000..2be8cec7d195f76d26c69d385e3eebd49efda1c0 --- /dev/null +++ b/x_track/src/App/Utils/lv_ext/lv_anim_timeline_wrapper.c @@ -0,0 +1,20 @@ +#include "lv_anim_timeline_wrapper.h" + +void lv_anim_timeline_add_wrapper(lv_anim_timeline_t* at, const lv_anim_timeline_wrapper_t* wrapper) +{ + for(uint32_t i = 0; wrapper[i].obj != NULL; i++) + { + const lv_anim_timeline_wrapper_t* atw = &wrapper[i]; + + lv_anim_t a; + lv_anim_init(&a); + lv_anim_set_var(&a, atw->obj); + lv_anim_set_values(&a, atw->start, atw->end); + lv_anim_set_exec_cb(&a, atw->exec_cb); + lv_anim_set_time(&a, atw->duration); + lv_anim_set_path_cb(&a, atw->path_cb); + lv_anim_set_early_apply(&a, atw->early_apply); + + lv_anim_timeline_add(at, atw->start_time, &a); + } +} diff --git a/x_track/src/App/Utils/lv_ext/lv_anim_timeline_wrapper.h b/x_track/src/App/Utils/lv_ext/lv_anim_timeline_wrapper.h new file mode 100644 index 0000000000000000000000000000000000000000..e80fa2f0bc642bd1703999148c774f46b2d63f8a --- /dev/null +++ b/x_track/src/App/Utils/lv_ext/lv_anim_timeline_wrapper.h @@ -0,0 +1,38 @@ +#ifndef LV_ANIM_TIMELINE_WRAPPER_H +#define LV_ANIM_TIMELINE_WRAPPER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "lvgl/lvgl.h" + +/*Data of anim_timeline*/ +typedef struct { + uint32_t start_time; + void* obj; + lv_anim_exec_xcb_t exec_cb; + int32_t start; + int32_t end; + uint16_t duration; + lv_anim_path_cb_t path_cb; + bool early_apply; +} lv_anim_timeline_wrapper_t; + +/********************** +* GLOBAL PROTOTYPES +**********************/ + +void lv_anim_timeline_add_wrapper(lv_anim_timeline_t* at, const lv_anim_timeline_wrapper_t* wrapper); + +/********************** +* MACROS +**********************/ + +#define LV_ANIM_TIMELINE_WRAPPER_END {0, NULL} + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_ANIM_TIMELINE_wrapper_H*/ diff --git a/x_track/src/App/Utils/lv_ext/lv_ext_func.cpp b/x_track/src/App/Utils/lv_ext/lv_ext_func.cpp new file mode 100644 index 0000000000000000000000000000000000000000..131ed4955740c4976d2d1de0beb712fcde4712eb --- /dev/null +++ b/x_track/src/App/Utils/lv_ext/lv_ext_func.cpp @@ -0,0 +1,39 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "lv_ext_func.h" + +lv_indev_t* lv_indev_search(lv_indev_type_t type) +{ + lv_indev_t* cur_indev = NULL; + for (;;) { + cur_indev = lv_indev_get_next(cur_indev); + if (!cur_indev) { + break; + } + + if (lv_indev_get_type(cur_indev) == type) { + return cur_indev; + } + } + return NULL; +} diff --git a/x_track/src/App/Utils/lv_ext/lv_ext_func.h b/x_track/src/App/Utils/lv_ext/lv_ext_func.h new file mode 100644 index 0000000000000000000000000000000000000000..49ad4f743aeeebaaf25edbcfd102a6871891dfc1 --- /dev/null +++ b/x_track/src/App/Utils/lv_ext/lv_ext_func.h @@ -0,0 +1,32 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __LV_EXT_FUNC_H +#define __LV_EXT_FUNC_H + +#include "lvgl/lvgl.h" + +#define LV_ANIM_EXEC(attr) (lv_anim_exec_xcb_t) lv_obj_set_##attr + +lv_indev_t* lv_indev_search(lv_indev_type_t type); + +#endif /* __LV_EXT_FUNC_H */ diff --git a/x_track/src/App/Utils/lv_msg/lv_msg.c b/x_track/src/App/Utils/lv_msg/lv_msg.c new file mode 100644 index 0000000000000000000000000000000000000000..882707de3b219b33bb6a66bd4cde954f5cb783f3 --- /dev/null +++ b/x_track/src/App/Utils/lv_msg/lv_msg.c @@ -0,0 +1,221 @@ +/** + * @file lv_msg.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_msg.h" +#include "lvgl/src/lvgl_private.h" + +/********************* + * DEFINES + *********************/ + +#define LV_GC_ROOT(x) (x) + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + lv_msg_id_t msg_id; + lv_msg_subscribe_cb_t callback; + void * user_data; + void * _priv_data; /*Internal: used only store 'obj' in lv_obj_subscribe*/ + uint8_t _checked : 1; /*Internal: used to prevent multiple notifications*/ +} sub_dsc_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void notify(lv_msg_t * m); +static void obj_notify_cb(lv_msg_t * m); +static void obj_delete_event_cb(lv_event_t * e); + +/********************** + * STATIC VARIABLES + **********************/ +static bool restart_notify; +static lv_ll_t _subs_ll; + +/********************** + * GLOBAL VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +#define MSG_TRACE(...) LV_LOG_TRACE(__VA_ARGS__) + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_msg_init(void) +{ + _lv_ll_init(&LV_GC_ROOT(_subs_ll), sizeof(sub_dsc_t)); +} + +void * lv_msg_subscribe(lv_msg_id_t msg_id, lv_msg_subscribe_cb_t cb, void * user_data) +{ + sub_dsc_t * s = _lv_ll_ins_tail(&LV_GC_ROOT(_subs_ll)); + LV_ASSERT_MALLOC(s); + if(s == NULL) return NULL; + + lv_memzero(s, sizeof(*s)); + + s->msg_id = msg_id; + s->callback = cb; + s->user_data = user_data; + s->_checked = 0; /*if subsribed during `notify`, it should be notified immediately*/ + restart_notify = true; + return s; +} + +void * lv_msg_subscribe_obj(lv_msg_id_t msg_id, lv_obj_t * obj, void * user_data) +{ + sub_dsc_t * s = lv_msg_subscribe(msg_id, obj_notify_cb, user_data); + if(s == NULL) return NULL; + s->_priv_data = obj; + + /*If not added yet, add a delete_event_cb which automatically unsubcribes the object when its deleted*/ + uint32_t i; + uint32_t event_cnt = lv_obj_get_event_count(obj); + sub_dsc_t * s_first = NULL; + for(i = 0; i < event_cnt; i++) { + lv_event_dsc_t * event_dsc = lv_obj_get_event_dsc(obj, i); + if(lv_event_dsc_get_cb(event_dsc) == obj_delete_event_cb) { + s_first = lv_event_dsc_get_user_data(event_dsc); + break; + } + } + + if(s_first == NULL) { + lv_obj_add_event(obj, obj_delete_event_cb, LV_EVENT_DELETE, s); + } + + return s; +} + +void lv_msg_unsubscribe(void * s) +{ + LV_ASSERT_NULL(s); + _lv_ll_remove(&LV_GC_ROOT(_subs_ll), s); + restart_notify = true; + lv_free(s); +} + +void lv_msg_send(lv_msg_id_t msg_id, const void * payload) +{ + lv_msg_t m; + lv_memzero(&m, sizeof(m)); + m.id = msg_id; + m.payload = payload; + notify(&m); +} + +void lv_msg_update_value(void * v) +{ + lv_msg_send((lv_msg_id_t)v, v); +} + +lv_msg_id_t lv_msg_get_id(lv_msg_t * m) +{ + return m->id; +} + +const void * lv_msg_get_payload(lv_msg_t * m) +{ + return m->payload; +} + +void * lv_msg_get_user_data(lv_msg_t * m) +{ + return m->user_data; +} + +lv_msg_t * lv_event_get_msg(lv_event_t * e) +{ + if(e->code == LV_EVENT_MSG_RECEIVED) { + return lv_event_get_param(e); + } + else { + LV_LOG_WARN("Not interpreted with this event code"); + return NULL; + } +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void notify(lv_msg_t * m) +{ + static unsigned int _recursion_counter = 0; + _recursion_counter++; + + /*First clear all _checked flags*/ + sub_dsc_t * s; + if(_recursion_counter == 1) { + _LV_LL_READ(&LV_GC_ROOT(_subs_ll), s) { + s->_checked = 0; + } + } + + /*Run all sub_dsc_t from the list*/ + do { + restart_notify = false; + s = _lv_ll_get_head(&LV_GC_ROOT(_subs_ll)); + while(s) { + /*get next element while current is surely valid*/ + sub_dsc_t * next = _lv_ll_get_next(&LV_GC_ROOT(_subs_ll), s); + + /*Notify only once*/ + if(!s->_checked) { + /*Check if this sub_dsc_t is about this msg_id*/ + if(s->msg_id == m->id && s->callback) { + // Set this flag and notify + s->_checked = 1; + m->user_data = s->user_data; + m->_priv_data = s->_priv_data; + s->callback(m); + } + } + + /*restart or load next*/ + if(restart_notify) { + MSG_TRACE("Start from the first sub_dsc_t again because _subs_ll may have changed"); + break; + } + s = next; + } + } while(s); + + _recursion_counter--; + restart_notify = (_recursion_counter > 0); +} + +static void obj_notify_cb(lv_msg_t * m) +{ + lv_obj_send_event(m->_priv_data, LV_EVENT_MSG_RECEIVED, m); +} + +static void obj_delete_event_cb(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target(e); + + sub_dsc_t * s = _lv_ll_get_head(&LV_GC_ROOT(_subs_ll)); + sub_dsc_t * s_next; + while(s) { + /*On unsubscribe the list changes s becomes invalid so get next item while it's surely valid*/ + s_next = _lv_ll_get_next(&LV_GC_ROOT(_subs_ll), s); + if(s->_priv_data == obj) { + lv_msg_unsubscribe(s); + } + s = s_next; + } +} diff --git a/x_track/src/App/Utils/lv_msg/lv_msg.h b/x_track/src/App/Utils/lv_msg/lv_msg.h new file mode 100644 index 0000000000000000000000000000000000000000..43e29e34e69bdb619254e78f97b0f696d85b8e19 --- /dev/null +++ b/x_track/src/App/Utils/lv_msg/lv_msg.h @@ -0,0 +1,132 @@ +/** + * @file lv_msg.h + * + */ + +#ifndef LV_MSG_H +#define LV_MSG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "lvgl/lvgl.h" + +/********************* + * DEFINES + *********************/ + +#define LV_EVENT_MSG_RECEIVED ((lv_event_code_t)(_LV_EVENT_LAST + 32)) + +/********************** + * TYPEDEFS + **********************/ + +typedef lv_uintptr_t lv_msg_id_t; + +typedef struct { + lv_msg_id_t id; /*Identifier of the message*/ + void * user_data; /*Set the the user_data set in `lv_msg_subscribe`*/ + void * _priv_data; /*Used internally*/ + const void * payload; /*Pointer to the data of the message*/ +} lv_msg_t; + + +typedef void (*lv_msg_subscribe_cb_t)(lv_msg_t * msg); + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Called internally to initialize the message module + */ +void lv_msg_init(void); + +/** + * Subscribe to an `msg_id` + * @param msg_id the message ID to listen to + * @param cb callback to call if a message with `msg_id` was sent + * @param user_data arbitrary data which will be available in `cb` too + * @return pointer to a "subscribe object". It can be used the unsubscribe. + */ +void * lv_msg_subscribe(lv_msg_id_t msg_id, lv_msg_subscribe_cb_t cb, void * user_data); + +/** + * Subscribe an `lv_obj` to a message. + * `LV_EVENT_MSG_RECEIVED` will be triggered if a message with matching ID was sent + * @param msg_id the message ID to listen to + * @param obj pointer to an `lv_obj` + * @param user_data arbitrary data which will be available in `cb` too + * @return pointer to a "subscribe object". It can be used the unsubscribe. + */ +void * lv_msg_subscribe_obj(lv_msg_id_t msg_id, lv_obj_t * obj, void * user_data); + +/** + * Cancel a previous subscription + * @param s pointer to a "subscibe object". + * Return value of `lv_msg_subscribe` or `lv_msg_subscribe_obj` + */ +void lv_msg_unsubscribe(void * s); + +/** + * Send a message with a given ID and payload + * @param msg_id ID of the message to send + * @param payload pointer to the data to send + */ +void lv_msg_send(lv_msg_id_t msg_id, const void * payload); + +/** + * Send a message where the message ID is `v` (the value of the pointer) + * and the payload is `v`. + * It can be used to send unique messages when a variable changed. + * @param v pointer to a variable. + * @note to subscribe to a variable use `lv_msg_subscribe((lv_msg_id_t)v, msg_cb, user_data)` + * or `lv_msg_subscribe_obj((lv_msg_id_t)v, obj, user_data)` + */ +void lv_msg_update_value(void * v); + +/** + * Get the ID of a message object. Typically used in the subscriber callback. + * @param m pointer to a message object + * @return the ID of the message + */ +lv_msg_id_t lv_msg_get_id(lv_msg_t * m); + +/** + * Get the payload of a message object. Typically used in the subscriber callback. + * @param m pointer to a message object + * @return the payload of the message + */ +const void * lv_msg_get_payload(lv_msg_t * m); + +/** + * Get the user data of a message object. Typically used in the subscriber callback. + * @param m pointer to a message object + * @return the user data of the message + */ +void * lv_msg_get_user_data(lv_msg_t * m); + +/** + * Get the message object from an event object. Can be used in `LV_EVENT_MSG_RECEIVED` events. + * @param e pointer to an event object + * @return the message object or NULL if called with unrelated event code. + */ +lv_msg_t * lv_event_get_msg(lv_event_t * e); + +/********************** + * GLOBAL VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_MSG_H*/ diff --git a/x_track/src/App/Utils/lv_poly_line/lv_poly_line.cpp b/x_track/src/App/Utils/lv_poly_line/lv_poly_line.cpp new file mode 100644 index 0000000000000000000000000000000000000000..64f25208a7ccf1dd4c00e4de518ba6e489add096 --- /dev/null +++ b/x_track/src/App/Utils/lv_poly_line/lv_poly_line.cpp @@ -0,0 +1,124 @@ +#include "lv_poly_line.h" + +lv_poly_line::lv_poly_line(lv_obj_t* par) + : current_index(0) + , styleLine(nullptr) + , parent(par) +{ +} + +lv_poly_line::~lv_poly_line() +{ + for (auto iter : poly_line) { + if (iter.line) { + lv_obj_del(iter.line); + } + + decltype(iter.points) vec; + iter.points.swap(vec); + } +} + +void lv_poly_line::add_line() +{ + lv_obj_t* line = lv_line_create(parent); + lv_obj_remove_style_all(line); + + if (styleLine) { + lv_obj_add_style(line, styleLine, 0); + } + + single_line_t single_line; + single_line.line = line; + poly_line.push_back(single_line); + + LV_LOG_INFO("line: %p", line); +} + +void lv_poly_line::start() +{ + if (current_index >= poly_line.size()) { + add_line(); + } + + LV_LOG_INFO("show poly_line[%" LV_PRId32 "]", current_index); + + single_line_t* single_line = &poly_line[current_index]; + + lv_obj_clear_flag(single_line->line, LV_OBJ_FLAG_HIDDEN); + lv_line_set_points(single_line->line, nullptr, 0); + + // const lv_palette_t palette[] = { LV_PALETTE_RED, LV_PALETTE_GREEN, LV_PALETTE_GREY }; + // lv_obj_set_style_line_color(single_line->line, lv_palette_main(palette[current_index % 3]), 0); + current_index++; +} + +void lv_poly_line::append(const lv_point_precise_t* point) +{ + if (current_index == 0) { + LV_LOG_WARN("NOT start"); + return; + } + + LV_LOG_INFO("poly_line[%" LV_PRId32 "]: (%d, %d)", current_index - 1, (int)point->x, (int)point->y); + single_line_t* single_line = &poly_line[current_index - 1]; + single_line->points.push_back(*point); + lv_line_set_points(single_line->line, get_points(single_line), single_line->points.size()); +} + +void lv_poly_line::reset() +{ + current_index = 0; + size_t size = poly_line.size(); + LV_LOG_INFO("poly_line.size() = %zu", size); + LV_LOG_INFO("reset current_index"); + + for (size_t i = 0; i < size; i++) { + single_line_t* single_line = &poly_line[i]; + lv_line_set_points(single_line->line, nullptr, 0); + single_line->points.clear(); + lv_obj_add_flag(single_line->line, LV_OBJ_FLAG_HIDDEN); + } +} + +lv_poly_line::single_line_t* lv_poly_line::get_end_line() +{ + if (current_index == 0) { + LV_LOG_INFO("NOT found"); + return nullptr; + } + + LV_LOG_INFO("end_line index = %" LV_PRId32, current_index - 1); + + return &poly_line[current_index - 1]; +} + +const lv_point_precise_t* lv_poly_line::get_points(single_line_t* single_line) +{ + const lv_point_precise_t* points = nullptr; + + if (single_line->points.size()) { + points = &single_line->points[0]; + } else { + LV_LOG_WARN("NOT found"); + } + + return points; +} + +bool lv_poly_line::get_end_point(lv_point_precise_t* point) +{ + single_line_t* single_line = get_end_line(); + if (!single_line) { + LV_LOG_INFO("end line NOT found"); + return false; + } + + if (single_line->points.size() == 0) { + LV_LOG_WARN("single_line->points is EMPTY"); + return false; + } + + *point = single_line->points.back(); + return true; +} diff --git a/x_track/src/App/Utils/lv_poly_line/lv_poly_line.h b/x_track/src/App/Utils/lv_poly_line/lv_poly_line.h new file mode 100644 index 0000000000000000000000000000000000000000..61fe9fdb5862d3f871a3e70e4c92d2a5c28f94c3 --- /dev/null +++ b/x_track/src/App/Utils/lv_poly_line/lv_poly_line.h @@ -0,0 +1,46 @@ +#ifndef LV_POLY_LINE_H +#define LV_POLY_LINE_H + +#include "lvgl/lvgl.h" +#include <vector> + +class lv_poly_line { +public: + lv_poly_line(lv_obj_t* par); + ~lv_poly_line(); + + void set_style(lv_style_t* style) + { + styleLine = style; + } + + void start(); + void append(const lv_point_precise_t* point); + void append(lv_coord_t x, lv_coord_t y) + { + lv_point_precise_t point = { (lv_value_precise_t)x, (lv_value_precise_t)y }; + append(&point); + } + void reset(); + bool get_end_point(lv_point_precise_t* point); + +private: + typedef struct + { + lv_obj_t* line; + std::vector<lv_point_precise_t> points; + } single_line_t; + +private: + void add_line(); + single_line_t* get_end_line(); + const lv_point_precise_t* get_points(single_line_t* single_line); + +private: + std::vector<single_line_t> poly_line; + uint32_t current_index; + lv_style_t* styleLine; + lv_obj_t* parent; +}; + +#endif // !LV_POLY_LINE_H diff --git a/x_track/src/App/Utils/lv_toast/lv_toast.c b/x_track/src/App/Utils/lv_toast/lv_toast.c new file mode 100644 index 0000000000000000000000000000000000000000..904960a821153d8c17299e6ae70bb1911d0ba6b6 --- /dev/null +++ b/x_track/src/App/Utils/lv_toast/lv_toast.c @@ -0,0 +1,151 @@ +/** + * @file lv_toast.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_toast.h" + +/********************* + * DEFINES + *********************/ + +#define MY_CLASS &lv_toast_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void lv_toast_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj); +static void lv_toast_event(const lv_obj_class_t* class_p, lv_event_t* e); +static void lv_toast_set_height(void* var, int32_t y); +static void lv_toast_set_opa(void* var, int32_t opa); +static void lv_toast_anim_del(lv_anim_t* a); + +/********************** + * STATIC VARIABLES + **********************/ + +const lv_obj_class_t lv_toast_class = { + .base_class = &lv_obj_class, + .constructor_cb = lv_toast_constructor, + .width_def = LV_DPI_DEF / 5, + .height_def = LV_DPI_DEF / 5, + .event_cb = lv_toast_event, + .instance_size = sizeof(lv_toast_t), +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t* lv_toast_create(lv_obj_t* parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t* obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + + lv_obj_remove_style_all(obj); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_align(obj, LV_ALIGN_BOTTOM_MID, 0, 0); + lv_obj_set_size(obj, lv_pct(100), LV_SIZE_CONTENT); + lv_obj_set_style_anim_time(obj, 200, 0); + lv_obj_set_style_pad_all(obj, 10, 0); + + lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICKABLE); + + return obj; +} + +void lv_toast_show_text(lv_obj_t* obj, const char* txt, uint32_t duration) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_toast_t* toast = (lv_toast_t*)obj; + + lv_obj_t* cont = lv_obj_create(obj); + lv_obj_clear_flag(cont, LV_OBJ_FLAG_CLICKABLE); + lv_obj_set_size(cont, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_scrollbar_mode(cont, LV_SCROLLBAR_MODE_OFF); + if (toast->style) { + lv_obj_add_style(cont, toast->style, 0); + } + + lv_obj_t* label = lv_label_create(cont); + lv_label_set_text(label, txt); + lv_obj_center(label); + + lv_obj_update_layout(cont); + lv_coord_t h = lv_obj_get_height(cont); + + uint32_t anim_time = lv_obj_get_style_anim_time(obj, 0); + + /* height anim */ + lv_anim_t a; + lv_anim_init(&a); + lv_anim_set_var(&a, cont); + lv_anim_set_exec_cb(&a, lv_toast_set_height); + lv_anim_set_values(&a, 0, h); + lv_anim_set_time(&a, anim_time); + lv_anim_set_path_cb(&a, lv_anim_path_ease_out); + lv_anim_start(&a); + + /* opa anim */ + lv_anim_set_delay(&a, anim_time + duration); + lv_anim_set_values(&a, LV_OPA_COVER, LV_OPA_TRANSP); + lv_anim_set_exec_cb(&a, lv_toast_set_opa); + lv_anim_set_deleted_cb(&a, lv_toast_anim_del); + lv_anim_start(&a); +} + +void lv_toast_set_cont_style(lv_obj_t* obj, lv_style_t* style) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_toast_t* toast = (lv_toast_t*)obj; + toast->style = style; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_toast_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj) +{ + LV_UNUSED(class_p); + lv_toast_t* toast = (lv_toast_t*)obj; + toast->style = NULL; +} + +static void lv_toast_event(const lv_obj_class_t* class_p, lv_event_t* e) +{ + LV_UNUSED(class_p); + lv_obj_event_base(MY_CLASS, e); +} + +static void lv_toast_set_height(void* var, int32_t y) +{ + lv_obj_set_height(var, y); +} + +static void lv_toast_set_opa(void* var, int32_t opa) +{ + lv_obj_set_style_opa(var, opa, 0); +} + +static void lv_toast_anim_del(lv_anim_t* a) +{ + lv_obj_del(a->var); +} diff --git a/x_track/src/App/Utils/lv_toast/lv_toast.h b/x_track/src/App/Utils/lv_toast/lv_toast.h new file mode 100644 index 0000000000000000000000000000000000000000..e3521adeacf5a025f48ad88a7b02df4d41ce7a4b --- /dev/null +++ b/x_track/src/App/Utils/lv_toast/lv_toast.h @@ -0,0 +1,53 @@ +/** + * @file lv_toast.h + * + */ + +#ifndef LV_TOAST_H +#define LV_TOAST_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "lvgl/lvgl.h" +#include "lvgl/src/lvgl_private.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + lv_obj_t obj; + lv_style_t* style; +} lv_toast_t; + +extern const lv_obj_class_t lv_toast_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +lv_obj_t* lv_toast_create(lv_obj_t* parent); + +void lv_toast_show_text(lv_obj_t* obj, const char* txt, uint32_t duration); + +void lv_toast_set_cont_style(lv_obj_t* obj, lv_style_t* style); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_TOAST_H*/ diff --git a/x_track/src/App/Version.h b/x_track/src/App/Version.h new file mode 100644 index 0000000000000000000000000000000000000000..5240e6a8da37c125084a21d085ea4cb696cae318 --- /dev/null +++ b/x_track/src/App/Version.h @@ -0,0 +1,33 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __VERSION_H +#define __VERSION_H + +/* Firmware Version */ +#define VERSION_FIRMWARE_NAME "X-TRACK" +#define VERSION_SOFTWARE "v2.7 (dev-lvgl-v9)" +#define VERSION_HARDWARE "v1.0" +#define VERSION_AUTHOR_NAME "_VIFEXTech" +#define VERSION_WEBSITE "https://github.com/FASTSHIFT/X-TRACK" + +#endif diff --git a/x_track/src/Frameworks/DataBroker/DataBroker.cpp b/x_track/src/Frameworks/DataBroker/DataBroker.cpp new file mode 100644 index 0000000000000000000000000000000000000000..245959077688e87023e0de091b8d6d066fc156a7 --- /dev/null +++ b/x_track/src/Frameworks/DataBroker/DataBroker.cpp @@ -0,0 +1,135 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "DataBroker.h" +#include "DataBrokerLog.h" +#include "DataTimer.h" +#include <algorithm> +#include <cstring> + +DataBroker::DataBroker(const char* name) + : _nodePool(nullptr) + , _mainNode(nullptr) + , _timerManager(nullptr) +{ + _name = name; + _nodePool = new DataNode::DataNodeList_t; + _mainNode = new DataNode(name, this); +} + +DataBroker::~DataBroker() +{ + DN_LOG_WARN("DataBroker[%s] closing...", _name); + + DN_LOG_INFO("Deleting main node: %p", _mainNode); + delete _mainNode; + _mainNode = nullptr; + + /* check leak */ + for (auto iter : *_nodePool) { + (void)iter; + DN_LOG_WARN("Leak: DataNode[%s]", iter->getID()); + } + + DN_LOG_INFO("Deleting node pool: %p", _nodePool); + delete _nodePool; + _nodePool = nullptr; + + if (_timerManager) { + DN_LOG_INFO("Deleting timer manager: %p", _timerManager); + delete _timerManager; + _timerManager = nullptr; + } + + DN_LOG_WARN("DataBroker[%s] closed.", _name); +} + +DataNode* DataBroker::search(const char* id) +{ + return search(_nodePool, id); +} + +DataNode* DataBroker::search(DataNode::DataNodeList_t* vec, const char* id) +{ + for (auto iter : *vec) { + if (strcmp(id, iter->getID()) == 0) { + return iter; + } + } + return nullptr; +} + +bool DataBroker::add(DataNode* node) +{ + if (_mainNode == nullptr || node == _mainNode) { + return false; + } + + if (search(node->getID()) != nullptr) { + DN_LOG_ERROR("Multi add DataNode[%s]", node->getID()); + return false; + } + + _nodePool->push_back(node); + + _mainNode->subscribe(node->getID()); + + return true; +} + +bool DataBroker::remove(DataNode* node) +{ + return remove(_nodePool, node); +} + +bool DataBroker::remove(DataNode::DataNodeList_t* vec, DataNode* node) +{ + auto iter = std::find(vec->begin(), vec->end(), node); + + if (iter == vec->end()) { + DN_LOG_ERROR("DataNode[%s] was not found", node->getID()); + return false; + } + + vec->erase(iter); + + return true; +} + +size_t DataBroker::getNodeNumber() +{ + return _nodePool->size(); +} + +void DataBroker::initTimerManager(uint32_t (*tickFunction)(void)) +{ + if (!_timerManager) { + _timerManager = new DataTimerManager; + } + + _timerManager->setTickCallback(tickFunction); +} + +uint32_t DataBroker::handleTimer() +{ + return _timerManager->handler(); +} diff --git a/x_track/src/Frameworks/DataBroker/DataBroker.h b/x_track/src/Frameworks/DataBroker/DataBroker.h new file mode 100644 index 0000000000000000000000000000000000000000..2e5f51fdccefd3956dcc04d4438a19c9c1498c8f --- /dev/null +++ b/x_track/src/Frameworks/DataBroker/DataBroker.h @@ -0,0 +1,133 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_BROKER_H +#define __DATA_BROKER_H + +#include "DataNode.h" + +class DataTimerManager; + +class DataBroker { + friend class DataNode; + +public: + /** + * @brief Data broker constructor + * @param name: The name of the data broker + */ + DataBroker(const char* name); + + /** + * @brief Data broker destructor + */ + ~DataBroker(); + + /** + * @brief Get the number of nodes in the node pool + * @retval Number of nodes + */ + size_t getNodeNumber(); + + /** + * @brief Get the main node + * @retval Pointer to the main node + */ + DataNode* mainNode() + { + return _mainNode; + } + + /** + * @brief Set the main node + * @param node: Pointer to the main node + */ + void initTimerManager(uint32_t (*tickFunction)(void)); + + /** + * @brief Handle timer + * @retval Time till next timer event in millisecond + */ + uint32_t handleTimer(); + +protected: + /** + * @brief Add an node to the node pool + * @param node: Pointer to node + * @retval If the addition is successful, return true + */ + bool add(DataNode* node); + + /** + * @brief Remove the node from the node pool + * @param node: Pointer to node + * @retval Return true if the removal is successful + */ + bool remove(DataNode* node); + + /** + * @brief Remove node in vector + * @param vec: Pointer to vector + * @param id: DataNode ID + * @retval Return true if the removal is successful + */ + bool remove(DataNode::DataNodeList_t* vec, DataNode* node); + + /** + * @brief Search node + * @param id: DataNode ID + * @retval If the search is successful, return the pointer of the node + */ + DataNode* search(const char* id); + + /** + * @brief Search node in vector + * @param vec: Pointer to vector + * @param id: DataNode ID + * @retval If the search is successful, return the pointer of the node + */ + DataNode* search(DataNode::DataNodeList_t* vec, const char* id); + + /** + * @brief Get the timer manager + * @retval Pointer to the timer manager + */ + DataTimerManager* timerManager() + { + return _timerManager; + } + +private: + /* DataNode pool */ + DataNode::DataNodeList_t* _nodePool; + + /* Main node, will automatically follow all nodes */ + DataNode* _mainNode; + + /* Timer manager */ + DataTimerManager* _timerManager; + + /* The name of the data center will be used as the ID of the main node */ + const char* _name; +}; + +#endif diff --git a/x_track/src/Frameworks/DataBroker/DataBrokerLog.h b/x_track/src/Frameworks/DataBroker/DataBrokerLog.h new file mode 100644 index 0000000000000000000000000000000000000000..58341028c18c20768cfff00f462c6e48e0d49931 --- /dev/null +++ b/x_track/src/Frameworks/DataBroker/DataBrokerLog.h @@ -0,0 +1,58 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_BROKER_LOG_H +#define __DATA_BROKER_LOG_H + +#define DATA_BROKER_LOG_LEVEL_INFO 0 +#define DATA_BROKER_LOG_LEVEL_WARN 1 +#define DATA_BROKER_LOG_LEVEL_ERROR 2 +#define DATA_BROKER_LOG_LEVEL_OFF 3 + +#ifndef DATA_BROKER_LOG_LEVEL +#define DATA_BROKER_LOG_LEVEL DATA_BROKER_LOG_LEVEL_OFF +#endif + +#if (DATA_BROKER_LOG_LEVEL < DATA_BROKER_LOG_LEVEL_OFF) +#include <stdio.h> +#define _DN_LOG(format, ...) printf("[DN]" format "\r\n", ##__VA_ARGS__) +#endif + +#if (DATA_BROKER_LOG_LEVEL <= DATA_BROKER_LOG_LEVEL_INFO) +#define DN_LOG_INFO(format, ...) _DN_LOG("[Info] " format, ##__VA_ARGS__) +#define DN_LOG_WARN(format, ...) _DN_LOG("[Warn] " format, ##__VA_ARGS__) +#define DN_LOG_ERROR(format, ...) _DN_LOG("[Error] " format, ##__VA_ARGS__) +#elif (DATA_BROKER_LOG_LEVEL <= DATA_BROKER_LOG_LEVEL_WARN) +#define DN_LOG_INFO(format, ...) +#define DN_LOG_WARN(format, ...) _DN_LOG("[Warn] " format, ##__VA_ARGS__) +#define DN_LOG_ERROR(format, ...) _DN_LOG("[Error] " format, ##__VA_ARGS__) +#elif (DATA_BROKER_LOG_LEVEL <= DATA_BROKER_LOG_LEVEL_ERROR) +#define DN_LOG_INFO(format, ...) +#define DN_LOG_WARN(format, ...) +#define DN_LOG_ERROR(format, ...) _DN_LOG("[Error] " format, ##__VA_ARGS__) +#else +#define DN_LOG_INFO(...) +#define DN_LOG_WARN(...) +#define DN_LOG_ERROR(...) +#endif + +#endif diff --git a/x_track/src/Frameworks/DataBroker/DataNode.cpp b/x_track/src/Frameworks/DataBroker/DataNode.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b8a9e3882109d2bce0b4bac0ba6f4b6307927713 --- /dev/null +++ b/x_track/src/Frameworks/DataBroker/DataNode.cpp @@ -0,0 +1,303 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "DataNode.h" +#include "DataBroker.h" +#include "DataBrokerLog.h" +#include "DataTimer.h" +#include <cstring> + +DataNode::DataNode( + const char* id, + DataBroker* broker, + void* userData) + : _ID(id) + , _broker(broker) + , _userData(userData) + , _eventCallback(nullptr) + , _eventFilterMask(0) + , _timer(nullptr) +{ + _broker->add(this); + + DN_LOG_INFO("DataNode[%s] created", _ID); +} + +DataNode::~DataNode() +{ + DN_LOG_INFO("DataNode[%s] deleting...", _ID); + + /* Delete timer */ + if (_timer) { + delete _timer; + _timer = nullptr; + } + + auto next = _subscribers.begin(); + decltype(next) current; + + /* Let subscribers unfollow */ + while (next != _subscribers.end()) { + current = next++; + DN_LOG_INFO("sub[%s] unsubscribed pub[%s]", (*current)->_ID, _ID); + (*current)->unsubscribe(_ID); + } + + /* Ask the publisher to delete this subscriber */ + for (auto iter : _publishers) { + DN_LOG_INFO("pub[%s] removed sub[%s]", iter->_ID, _ID); + _broker->remove(&iter->_subscribers, this); + } + + /* Let the data broker delete the node */ + _broker->remove(this); + DN_LOG_INFO("DataNode[%s] deleted", _ID); +} + +const DataNode* DataNode::subscribe(const char* pubID) +{ + /* Not allowed to subscribe to itself */ + if (strcmp(pubID, _ID) == 0) { + DN_LOG_ERROR("DataNode[%s] try to subscribe to it itself", _ID); + return nullptr; + } + + /* Check subscribe repeatedly */ + DataNode* pub = _broker->search(&_publishers, pubID); + if (pub) { + DN_LOG_WARN("Multi subscribe pub[%s]", pubID); + return pub; + } + + /* Check node is created */ + pub = _broker->search(pubID); + if (!pub) { + DN_LOG_ERROR("pub[%s] was not found", pubID); + return nullptr; + } + + /* Add the publisher to the subscription list */ + _publishers.push_back(pub); + + /* Let the publisher add this subscriber */ + pub->_subscribers.push_back(this); + + DN_LOG_INFO("sub[%s] subscribed pub[%s]", _ID, pubID); + + return pub; +} + +bool DataNode::unsubscribe(const char* pubID) +{ + /* Whether to subscribe to the publisher */ + DataNode* pub = _broker->search(&_publishers, pubID); + if (!pub) { + DN_LOG_WARN("sub[%s] was not subscribe pub[%s]", _ID, pubID); + return false; + } + + /* Remove the publisher from the subscription list */ + _broker->remove(&_publishers, pub); + + /* Let the publisher remove this subscriber */ + _broker->remove(&pub->_subscribers, this); + + return true; +} + +int DataNode::publish(const void* data_p, size_t size) +{ + int retval = RES_OK; + + EventParam_t param(this, nullptr, EVENT_PUBLISH, (void*)data_p, size); + + auto next = _subscribers.begin(); + /* Publish messages to subscribers */ + while (next != _subscribers.end()) { + auto sub = *next++; + param.recv = sub; + + DN_LOG_INFO("pub[%s] publish >> data(%p)[%zu] >> sub[%s]...", + _ID, param.data_p, param.size, sub->_ID); + + retval = sendEvent(sub, ¶m); + DN_LOG_INFO("publish done: %d", retval); + + /* record the last error code */ + if (retval == RES_STOP_PROCESS) { + DN_LOG_INFO("publish stop"); + break; + } + } + + return retval; +} + +int DataNode::pull(const char* pubID, void* data_p, size_t size) +{ + DataNode* pub = _broker->search(&_publishers, pubID); + if (!pub) { + DN_LOG_ERROR("sub[%s] was not subscribe pub[%s]", _ID, pubID); + return RES_NOT_FOUND; + } + return pull(pub, data_p, size); +} + +int DataNode::pull(const DataNode* pub, void* data_p, size_t size) +{ + if (!pub) { + return RES_PARAM_ERROR; + } + + DN_LOG_INFO("sub[%s] pull << data(%p)[%zu] << pub[%s] ...", + _ID, data_p, size, pub->_ID); + + EventCallback_t callback = pub->_eventCallback; + if (!callback) { + return RES_NO_CALLBACK; + } + + EventParam_t param(this, pub, EVENT_PULL, data_p, size); + int ret = sendEvent((DataNode*)pub, ¶m); + + DN_LOG_INFO("pull done: %d", ret); + return ret; +} + +int DataNode::notify(const char* pubID, const void* data_p, size_t size) +{ + DataNode* pub = _broker->search(&_publishers, pubID); + if (!pub) { + DN_LOG_ERROR("sub[%s] was not subscribe pub[%s]", _ID, pubID); + return RES_NOT_FOUND; + } + + return notify(pub, data_p, size); +} + +int DataNode::notify(const DataNode* pub, const void* data_p, size_t size) +{ + if (!pub) { + return RES_PARAM_ERROR; + } + + DN_LOG_INFO("sub[%s] notify >> data(%p)[%zu] >> pub[%s] ...", + _ID, data_p, size, pub->_ID); + + EventParam_t param(this, pub, EVENT_NOTIFY, (void*)data_p, size); + int ret = sendEvent((DataNode*)pub, ¶m); + + DN_LOG_INFO("send done: %d", ret); + + return ret; +} + +void DataNode::setEventCallback(EventCallback_t callback, uint32_t eventFilter) +{ + _eventCallback = callback; + _eventFilterMask = eventFilter; +} + +void DataNode::setEventFilter(uint32_t eventFilter) +{ + _eventFilterMask = eventFilter; +} + +int DataNode::onEvent(EventParam_t* param) +{ + if (!_eventCallback) { + DN_LOG_INFO("DataNode[%s] not register callback", _ID); + return RES_NO_CALLBACK; + } + + return _eventCallback(this, param); +} + +int DataNode::sendEvent(DataNode* node, EventParam_t* param) +{ + if (!(node->_eventFilterMask & param->event)) { + return RES_UNSUPPORTED_REQUEST; + } + + return node->onEvent(param); +} + +void DataNode::timerCallbackHandler(DataTimer* timer) +{ + auto instance = (DataNode*)timer->getUserData(); + + EventParam_t param(instance, instance, EVENT_TIMER, nullptr, 0); + instance->sendEvent(instance, ¶m); +} + +void DataNode::startTimer(uint32_t period) +{ + if (_timer) { + _timer->setPeriod(period); + _timer->resume(); + return; + } + + _timer = new DataTimer(_broker->timerManager(), timerCallbackHandler, period, this); +} + +void DataNode::setTimerPeriod(uint32_t period) +{ + if (!_timer) { + DN_LOG_ERROR("Timer not created"); + return; + } + + _timer->setPeriod(period); + _timer->resume(); +} + +void DataNode::resetTimer() +{ + if (!_timer) { + DN_LOG_ERROR("Timer not created"); + return; + } + + _timer->resume(); +} + +void DataNode::stopTimer() +{ + if (!_timer) { + return; + } + + _timer->pause(); + + DN_LOG_INFO("DataNode[%s] timer stop", _ID); +} + +size_t DataNode::getPublishersNumber() +{ + return _publishers.size(); +} + +size_t DataNode::getSubscribersNumber() +{ + return _subscribers.size(); +} diff --git a/x_track/src/Frameworks/DataBroker/DataNode.h b/x_track/src/Frameworks/DataBroker/DataNode.h new file mode 100644 index 0000000000000000000000000000000000000000..07a4a44327fc3bbda48158a2092095a77f3e0284 --- /dev/null +++ b/x_track/src/Frameworks/DataBroker/DataNode.h @@ -0,0 +1,265 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_NODE_H +#define __DATA_NODE_H + +#include <cstddef> +#include <cstdint> +#include <list> + +class DataBroker; +class DataTimer; + +class DataNode { +public: + /* Event type enumeration */ + typedef enum { + EVENT_NONE = 0, /* No event */ + EVENT_PUBLISH = 1 << 0, /* Publisher posted information */ + EVENT_PULL = 1 << 1, /* Subscriber data pull request */ + EVENT_NOTIFY = 1 << 2, /* Subscribers send notifications to publishers */ + EVENT_TIMER = 1 << 3, /* Timer event */ + EVENT_ALL = 0xFFFFFFFF /* All Event */ + } EventCode_t; + + /* Error type enumeration */ + typedef enum { + RES_OK = 0, + RES_UNKNOWN = -1, + RES_SIZE_MISMATCH = -2, + RES_UNSUPPORTED_REQUEST = -3, + RES_NO_CALLBACK = -4, + RES_NO_DATA = -5, + RES_NOT_FOUND = -6, + RES_PARAM_ERROR = -7, + RES_STOP_PROCESS = -8, + } ResCode_t; + + /* Event parameter structure */ + typedef struct EventParam { + EventParam(const DataNode* _tran, const DataNode* _recv, EventCode_t _event, void* _data_p, size_t _size) + : tran(_tran) + , recv(_recv) + , event(_event) + , data_p(_data_p) + , size(_size) + { + } + const DataNode* tran; /* Pointer to sender */ + const DataNode* recv; /* Pointer to receiver */ + EventCode_t event; /* Event type */ + void* data_p; /* Pointer to data */ + size_t size; /* The size of the data */ + } EventParam_t; + + /* Event callback function pointer */ + typedef int (*EventCallback_t)(DataNode* node, EventParam_t* param); + + typedef std::list<DataNode*> DataNodeList_t; + +public: + /** + * @brief DataNode constructor + * @param id: Unique name + * @param broker: Pointer to the data broker + * @param bufSize: The length of the data to be cached + * @param userData: Point to the address of user-defined data + */ + DataNode( + const char* id, + DataBroker* broker, + void* userData = nullptr); + + /** + * @brief DataNode destructor + */ + virtual ~DataNode(); + + /** + * @brief Subscribe to Publisher + * @param pubID: Publisher _ID + * @retval Pointer to publisher + */ + const DataNode* subscribe(const char* pubID); + + /** + * @brief Unsubscribe from publisher + * @param pubID: Publisher _ID + * @retval Return true if unsubscribe is successful + */ + bool unsubscribe(const char* pubID); + + /** + * @brief Get the number of publishers + * @retval number of publishers + */ + size_t getPublishersNumber(); + + /** + * @brief Get the number of subscribes + * @retval number of subscribes + */ + size_t getSubscribersNumber(); + + /** + * @brief Publish data to subscribers + * @param data_p: Pointer to data + * @param size: The size of the data + * @retval error code + */ + int publish(const void* data_p, size_t size); + + /** + * @brief Pull data from the publisher + * @param pubID: Publisher _ID + * @param data_p: Pointer to data + * @param size: The size of the data + * @retval error code + */ + int pull(const char* pubID, void* data_p, size_t size); + + /** + * @brief Send notification to the publisher + * @param pub: Publisher node pointer + * @param data_p: Pointer to data + * @param size: The size of the data + * @retval error code + */ + int pull(const DataNode* pub, void* data_p, size_t size); + + /** + * @brief Send a notification to the publisher + * @param pubID: Publisher _ID + * @param data_p: Pointer to data + * @param size: The size of the data + * @retval error code + */ + int notify(const char* pubID, const void* data_p, size_t size); + + /** + * @brief Send a notification to the publisher + * @param pub: Pointer to publisher + * @param data_p: Pointer to data + * @param size: The size of the data + * @retval error code + */ + int notify(const DataNode* pub, const void* data_p, size_t size); + + /** + * @brief Set event callback + * @param callback: Callback function pointer + * @param eventFilter: Event filter mask + */ + void setEventCallback(EventCallback_t callback, uint32_t eventFilter = EVENT_ALL); + + /** + * @brief Set event filter + * @param eventFilter: Event filter mask + */ + void setEventFilter(uint32_t eventFilter); + + /** + * @brief Event handler + * @param param: Event parameter structure + * @retval error code + */ + virtual int onEvent(EventParam_t* param); + + /** + * @brief Start timer + * @param period: Timer period + */ + void startTimer(uint32_t period); + + /** + * @brief Change timer period + * @param period: Timer period + */ + void setTimerPeriod(uint32_t period); + + /** + * @brief Reset timer + */ + void resetTimer(); + + /** + * @brief Stop timer + */ + void stopTimer(); + + /** + * @brief Set user-defined data + * @param userData: Point to the address of user-defined data + */ + void setUserData(void* userData) + { + _userData = userData; + } + + /** + * @brief Get user-defined data + * @retval Point to the address of user-defined data + */ + void* getUserData() + { + return _userData; + } + + /** + * @brief Get node ID + * @retval Node ID + */ + const char* getID() const + { + return _ID; + } + +private: + DataNodeList_t _publishers; /* Followed publishers */ + DataNodeList_t _subscribers; /* Followed subscribers */ + + const char* _ID; /* Node ID */ + DataBroker* _broker; /* Pointer to the data broker */ + void* _userData; + + EventCallback_t _eventCallback; + uint32_t _eventFilterMask; + DataTimer* _timer; + +private: + /** + * @brief Timer callback entry function + * @param timer: Pointer to timer + */ + static void timerCallbackHandler(DataTimer* timer); + + /** + * @brief Send event to the receiver + * @param node: Receiver node pointer + * @param param: Event parameter structure + * @retval error code + */ + static int sendEvent(DataNode* node, EventParam_t* param); +}; + +#endif diff --git a/x_track/src/Frameworks/DataBroker/DataTimer.cpp b/x_track/src/Frameworks/DataBroker/DataTimer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..80ff8de63db114f74aea1b976529d5005554fa77 --- /dev/null +++ b/x_track/src/Frameworks/DataBroker/DataTimer.cpp @@ -0,0 +1,174 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "DataTimer.h" + +#ifndef UINT32_MAX +#define UINT32_MAX 0xFFFFFFFF +#endif + +DataTimer::DataTimer(DataTimerManager* manager, TimerFunction_t timerFunc, uint32_t period, void* userData) + : _manager(manager) + , _timerFunc(timerFunc) + , _userData(userData) + , _period(period) + , _lastTick(0) + , _isPause(false) +{ + _manager->add(this); +} + +DataTimer::~DataTimer() +{ + _manager->remove(this); +} + +void DataTimer::pause() +{ + _isPause = true; +} + +void DataTimer::resume() +{ + _isPause = false; +} + +void DataTimer::reset() +{ + _lastTick = _manager->getTick(); +} + +uint32_t DataTimer::remain() +{ + auto elaps = _manager->getTickElaps(_lastTick); + return elaps > _period ? 0 : _period - elaps; +} + +void DataTimer::setPeriod(uint32_t period) +{ + _period = period; +} + +uint32_t DataTimer::getPeriod() +{ + return _period; +} + +void* DataTimer::getUserData() +{ + return _userData; +} + +bool DataTimer::isReady() +{ + if (_isPause) { + return false; + } + + if (remain() > 0) { + return false; + } + + return true; +} + +bool DataTimer::isPause() +{ + return _isPause; +} + +void DataTimer::invoke() +{ + _timerFunc(this); +} + +DataTimerManager::DataTimerManager(TickFunction_t tickCallback) +{ + _getTick = tickCallback; +} + +DataTimerManager::~DataTimerManager() +{ +} + +void DataTimerManager::setTickCallback(TickFunction_t tickCallback) +{ + _getTick = tickCallback; +} + +void DataTimerManager::add(DataTimer* timer) +{ + _timerList.push_back(timer); +} + +void DataTimerManager::remove(DataTimer* timer) +{ + _timerList.remove(timer); +} + +uint32_t DataTimerManager::getTick() +{ + return _getTick(); +} + +uint32_t DataTimerManager::getTickElaps(uint32_t prevTick) +{ + uint32_t act_time = getTick(); + + /*If there is no overflow in sys_time simple subtract*/ + if (act_time >= prevTick) { + prevTick = act_time - prevTick; + } else { + prevTick = UINT32_MAX - prevTick + 1; + prevTick += act_time; + } + + return prevTick; +} + +uint32_t DataTimerManager::handler() +{ + /* invoke all timers that are ready */ + for (const auto& iter : _timerList) { + if (iter->isReady()) { + iter->reset(); + iter->invoke(); + } + } + + uint32_t timeTillNext = UINT32_MAX; + + /* Find the minimum remaining time */ + for (const auto& iter : _timerList) { + /* Ignore paused timers */ + if (iter->isPause()) { + continue; + } + + uint32_t remain = iter->remain(); + if (remain < timeTillNext) { + timeTillNext = remain; + } + } + + return timeTillNext; +} diff --git a/x_track/src/Frameworks/DataBroker/DataTimer.h b/x_track/src/Frameworks/DataBroker/DataTimer.h new file mode 100644 index 0000000000000000000000000000000000000000..e779ec0e66b1a7a0d605fe328c0a7ff39e9f272e --- /dev/null +++ b/x_track/src/Frameworks/DataBroker/DataTimer.h @@ -0,0 +1,170 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __DATA_TIMER_H +#define __DATA_TIMER_H + +#include <cstdint> +#include <list> + +class DataTimerManager; + +class DataTimer { + friend class DataTimerManager; + +public: + typedef void (*TimerFunction_t)(DataTimer*); + +public: + /** + * @brief create a timer + * @param manager the timer manager + * @param timerFunc the timer function to be invoked + * @param period the timer period in millisecond + * @param userData the user data to be passed to the timer function + */ + DataTimer(DataTimerManager* manager, TimerFunction_t timerFunc, uint32_t period, void* userData = nullptr); + + ~DataTimer(); + + /** + * @brief pause the timer + */ + void pause(); + + /** + * @brief resume the timer + */ + void resume(); + + /** + * @brief reset the timer + */ + void reset(); + + /** + * @brief get the remaining time of the timer + * @return the remaining time in millisecond + */ + uint32_t remain(); + + /** + * @brief set the timer period + * @param period the timer period in millisecond + */ + void setPeriod(uint32_t period); + + /** + * @brief get the timer period + * @return the timer period in millisecond + */ + uint32_t getPeriod(); + + /** + * @brief get the user data + * @return the user data + */ + void* getUserData(); + +protected: + /** + * @brief get the timer is ready to invoke + * @return true if the timer is ready to invoke, false otherwise + */ + bool isReady(); + + /** + * @brief get the timer is pause + * @return true if the timer is pause, false otherwise + */ + bool isPause(); + + /** + * @brief invoke the timer function + */ + void invoke(); + +private: + DataTimerManager* _manager; + TimerFunction_t _timerFunc; + void* _userData; + uint32_t _period; + uint32_t _lastTick; + bool _isPause; +}; + +class DataTimerManager { +public: + typedef uint32_t (*TickFunction_t)(void); + +public: + /** + * @brief create a timer manager + * @param tickCallback the tick callback function to get the current tick + */ + DataTimerManager(TickFunction_t tickCallback = nullptr); + + ~DataTimerManager(); + + /** + * @brief set the tick callback function + * @param tickCallback the tick callback function to get the current tick + */ + void setTickCallback(TickFunction_t tickCallback); + + /** + * @brief add a timer + * @param timer the timer to be added + */ + void add(DataTimer* timer); + + /** + * @brief remove a timer + * @param timer the timer to be removed + */ + void remove(DataTimer* timer); + + /** + * @brief get the current tick + * @return the current tick + */ + uint32_t getTick(); + + /** + * @brief get the elapsed time from the previous tick + * @param prevTick the previous tick + * @return the elapsed time in millisecond + */ + uint32_t getTickElaps(uint32_t prevTick); + + /** + * @brief handle the timer events + * @return time till next timer event in millisecond + */ + uint32_t handler(); + +private: + TickFunction_t _getTick; + std::list<DataTimer*> _timerList; +}; + +#endif /* __DATA_TIMER_H */ diff --git a/x_track/src/Frameworks/DeviceManager/DeviceManager.cpp b/x_track/src/Frameworks/DeviceManager/DeviceManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e2dfc63c8148d284160ecc5ea931c309142b30f8 --- /dev/null +++ b/x_track/src/Frameworks/DeviceManager/DeviceManager.cpp @@ -0,0 +1,58 @@ +/* + * MIT License + * Copyright (c) 2022 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "DeviceManager.h" +#include "DeviceObject.h" +#include <string.h> + +DeviceManager::DeviceManager(DeviceObject** devArray, size_t len) +{ + _devArray = devArray; + _devArrayLen = len; +} + +DeviceManager::~DeviceManager() +{ +} + +void DeviceManager::init(InitFinishCallback_t callback) +{ + for (size_t i = 0; i < _devArrayLen; i++) { + DeviceObject* dev = _devArray[i]; + int retval = dev->init(); + if (callback) { + callback(this, dev, retval); + } + } +} + +DeviceObject* DeviceManager::getDevice(const char* name) +{ + for (size_t i = 0; i < _devArrayLen; i++) { + DeviceObject* dev = _devArray[i]; + if (strcmp(name, dev->getName()) == 0) { + return dev; + } + } + return nullptr; +} diff --git a/x_track/src/Frameworks/DeviceManager/DeviceManager.h b/x_track/src/Frameworks/DeviceManager/DeviceManager.h new file mode 100644 index 0000000000000000000000000000000000000000..58df406a99ccfa718ff3f55f835ba8e9c7b6ca45 --- /dev/null +++ b/x_track/src/Frameworks/DeviceManager/DeviceManager.h @@ -0,0 +1,47 @@ +/* + * MIT License + * Copyright (c) 2022 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __DEVICE_MANAGER_H +#define __DEVICE_MANAGER_H + +#include <stddef.h> + +class DeviceObject; + +class DeviceManager { +public: + typedef void (*InitFinishCallback_t)(DeviceManager* manager, DeviceObject* dev, int retval); + +public: + DeviceManager(DeviceObject** devArray, size_t len); + ~DeviceManager(); + + void init(InitFinishCallback_t callback = nullptr); + DeviceObject* getDevice(const char* name); + +private: + DeviceObject** _devArray; + size_t _devArrayLen; +}; + +#endif // !__DEVICE_MANAGER diff --git a/x_track/src/Frameworks/DeviceManager/DeviceObject.h b/x_track/src/Frameworks/DeviceManager/DeviceObject.h new file mode 100644 index 0000000000000000000000000000000000000000..f448b89226ac6fc04071d9e51198b63ca1e418b5 --- /dev/null +++ b/x_track/src/Frameworks/DeviceManager/DeviceObject.h @@ -0,0 +1,139 @@ +/* + * MIT License + * Copyright (c) 2022 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __DEVICE_OBJECT_H +#define __DEVICE_OBJECT_H + +#include <stddef.h> +#include <stdint.h> + +#define DEVICE_OBJECT_IOCMD_DEF(dir, size, type, number) \ + (uint32_t)( \ + (((uint32_t)(dir) & 0x0003) << 30) \ + | (((uint32_t)(size) & 0x3FFF) << 16) \ + | (((uint32_t)(type) & 0x00FF) << 8) \ + | ((uint32_t)(number) & 0x00FF)) + +class DeviceObject { +public: + typedef enum { + DIR_NONE = 0x00, + DIR_IN = 0x01, + DIR_OUT = 0x02, + DIR_IN_OUT = 0x03, + } Direction_t; + + typedef union { + uint32_t full; + struct + { + uint32_t number : 8; + uint32_t type : 8; + uint32_t size : 14; + uint32_t dir : 2; + } ch; + } IO_Cmd_t; + + typedef enum { + RES_OK = 0, + RES_UNKNOWN = -1, + RES_NO_IMPLEMENTED = -2, + RES_INIT_FAILED = -3, + RES_PARAM_ERROR = -4, + RES_UNSUPPORT = -5, + } ResCode_t; + +public: + DeviceObject(const char* name) + : _name(name) + , initOK(false) + { + } + virtual ~DeviceObject() {}; + + int init() + { + int retval = onInit(); + initOK = retval >= RES_OK; + + return retval; + } + + int read(void* buffer, size_t size) + { + if (!initOK) { + return RES_INIT_FAILED; + } + + if (!buffer) { + return RES_PARAM_ERROR; + } + + return onRead(buffer, size); + } + + int write(const void* buffer, size_t size) + { + if (!initOK) { + return RES_INIT_FAILED; + } + + if (!buffer) { + return RES_PARAM_ERROR; + } + + return onWrite(buffer, size); + } + + int ioctl(uint32_t cmd, void* data = nullptr, size_t size = 0) + { + IO_Cmd_t* c = (IO_Cmd_t*)&cmd; + + if (!initOK) { + return RES_INIT_FAILED; + } + + if (size != c->ch.size) { + return RES_PARAM_ERROR; + } + + return onIoctl(*c, data); + } + + const char* getName() + { + return _name; + } + +private: + const char* _name; + bool initOK; + +private: + virtual int onInit() { return RES_NO_IMPLEMENTED; }; + virtual int onRead(void* buffer, size_t size) { return RES_NO_IMPLEMENTED; } + virtual int onWrite(const void* buffer, size_t size) { return RES_NO_IMPLEMENTED; } + virtual int onIoctl(IO_Cmd_t cmd, void* data) { return RES_NO_IMPLEMENTED; } +}; + +#endif diff --git a/x_track/src/Frameworks/PageManager/PageBase.cpp b/x_track/src/Frameworks/PageManager/PageBase.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d2fa33504dfb9aa514fb898ac6258803f2e3f4c2 --- /dev/null +++ b/x_track/src/Frameworks/PageManager/PageBase.cpp @@ -0,0 +1,92 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "PageBase.h" +#include "PageLog.h" + +PageBase::PageBase() + : _manager(nullptr) + , _name(nullptr) + , _id(0) + , _root(nullptr) + , _context { 0 } +{ +} + +PageBase::~PageBase() +{ +} + +void PageBase::setCacheEnable(bool en) +{ + PAGE_LOG_INFO("Page(%s) enable %d", _name, en); + setAutoCacheEnable(false); + _context.reqEnableCache = en; +} + +void PageBase::setAutoCacheEnable(bool en) +{ + PAGE_LOG_INFO("Page(%s) enable %d", _name, en); + _context.reqDisableAutoCache = !en; +} + +void PageBase::setLoadAnimType(PAGE_ANIM type) +{ + _context.anim.attr.type = type; +} + +void PageBase::setLoadAnimTime(uint32_t time) +{ + _context.anim.attr.duration = time; +} + +void PageBase::setLoadAnimPath(lv_anim_path_cb_t path) +{ + _context.anim.attr.path = path; +} + +void PageBase::setBackGestureDirection(lv_dir_t dir) +{ + _context.backGestureDir = dir; +} + +bool PageBase::getParam(void* ptr, uint32_t size) +{ + if (!_context.param.ptr) { + PAGE_LOG_WARN("No param found"); + return false; + } + + if (_context.param.size != size) { + PAGE_LOG_WARN( + "param[%p](%" PRIu32 ") does not match the size(%" PRIu32 ")", + _context.param.ptr, + _context.param.size, + size); + return false; + } + + lv_memcpy(ptr, _context.param.ptr, _context.param.size); + lv_free(_context.param.ptr); + _context.param.ptr = nullptr; + return true; +} diff --git a/x_track/src/Frameworks/PageManager/PageBase.h b/x_track/src/Frameworks/PageManager/PageBase.h new file mode 100644 index 0000000000000000000000000000000000000000..562ebb4843c30065c3f5051a2eaa938151e6c553 --- /dev/null +++ b/x_track/src/Frameworks/PageManager/PageBase.h @@ -0,0 +1,232 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __PAGE_BASE_H +#define __PAGE_BASE_H + +#include "PageDef.h" +#include "lvgl/lvgl.h" + +/* Generate page param */ +#define PAGE_PARAM_MAKE(PARAM) \ + { \ + &(PARAM), sizeof(PARAM) \ + } + +/* Get page param */ +#define PAGE_GET_PARAM(PARAM) this->getParam(&(PARAM), sizeof(PARAM)) + +class PageManager; + +class PageBase { + friend class PageManager; + +public: + /* Page statement */ + enum class STATE { + IDLE, + LOAD, + WILL_APPEAR, + DID_APPEAR, + ACTIVITY, + WILL_DISAPPEAR, + DID_DISAPPEAR, + UNLOAD, + }; + +public: + PageBase(); + virtual ~PageBase(); + + /** + * @brief Page installation callback + */ + virtual void onInstalled() { } + + /** + * @brief Page load start callback + */ + virtual void onViewLoad() { } + + /** + * @brief Page load end callback + */ + virtual void onViewDidLoad() { } + + /** + * @brief Page appear animation start callback + */ + virtual void onViewWillAppear() { } + + /** + * @brief Page appear animation end callback + */ + virtual void onViewDidAppear() { } + + /** + * @brief Page disappear animation start callback + */ + virtual void onViewWillDisappear() { } + + /** + * @brief Page disappear animation end callback + */ + virtual void onViewDidDisappear() { } + + /** + * @brief Page unload start callback + */ + virtual void onViewUnload() { } + + /** + * @brief Page unload end callback + */ + virtual void onViewDidUnload() { } + + /** + * @brief Set page cache enable + * @param en: Cache enable + * @retval None + */ + void setCacheEnable(bool en); + + /** + * @brief Set page automatic cache management enable + * @param en: Automatic cache management enable + * @retval None + */ + void setAutoCacheEnable(bool en); + + /** + * @brief Set page animation type + * @param type: Animation type + * @retval None + */ + void setLoadAnimType(PAGE_ANIM type); + + /** + * @brief Set page animation time + * @param time: Animation time + * @retval None + */ + void setLoadAnimTime(uint32_t time); + + /** + * @brief Set page animation path + * @param path: Animation path + * @retval None + */ + void setLoadAnimPath(lv_anim_path_cb_t path); + + /** + * @brief Set page back gesture direction + * @param dir: Back gesture direction, See lv_dir_t + * @retval None + */ + void setBackGestureDirection(lv_dir_t dir); + + /** + * @brief Get page parameter + * @param ptr: Parameter pointer + * @param size: Parameter size + * @retval return true if success + */ + bool getParam(void* ptr, uint32_t size); + + /** + * @brief Get page manager + * @param None + * @retval Page manager pointer + */ + PageManager* getManager() { return _manager; } + + /** + * @brief Get page name + * @param None + * @retval Page name + */ + const char* getName() { return _name; } + + /** + * @brief Get page ID + * @param None + * @retval Page ID + */ + uint16_t getID() { return _id; } + + /** + * @brief Get page root node + * @param None + * @retval Page root node + */ + lv_obj_t* getRoot() { return _root; } + +protected: + /* Page parameter */ + struct PARAM { + void* ptr; + uint32_t size; + }; + +private: + /* Page switching animation properties */ + struct ANIM_ATTR { + ANIM_ATTR() + : type(PAGE_ANIM::GLOBAL) + , duration(PAGE_ANIM_TIME_DEFAULT) + , path(PAGE_ANIM_PATH_DEFAULT) + { + } + PAGE_ANIM type; + uint32_t duration; + lv_anim_path_cb_t path; + }; + + struct CONTEXT { + bool reqEnableCache; /* Cache enable request */ + bool reqDisableAutoCache; /* Automatic cache management enable request */ + + bool isDisableAutoCache; /* Whether it is automatic cache management */ + bool isCached; /* Cache enable */ + + PARAM param; /* Page parameter */ + STATE state; /* Page statement */ + + struct + { + bool isEnter; /* Animation enter or exit */ + bool isBusy; /* Animation is busy */ + ANIM_ATTR attr; /* Animation properties */ + } anim; + + lv_dir_t backGestureDir; /* Back gesture direction */ + }; + +private: + PageManager* _manager; /* Page manager pointer */ + const char* _name; /* Page name */ + uint16_t _id; /* Page ID */ + lv_obj_t* _root; /* UI root node */ + CONTEXT _context; /* Page context */ +}; + +#endif /* __PAGE_BASE_H */ diff --git a/x_track/src/Frameworks/PageManager/PageDef.h b/x_track/src/Frameworks/PageManager/PageDef.h new file mode 100644 index 0000000000000000000000000000000000000000..b1e40d24a04847ea71f080977cefc54ddd7d7294 --- /dev/null +++ b/x_track/src/Frameworks/PageManager/PageDef.h @@ -0,0 +1,54 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __PAGE_DEF_H +#define __PAGE_DEF_H + +#define PAGE_ANIM_TIME_DEFAULT 300 //[ms] + +#define PAGE_ANIM_PATH_DEFAULT lv_anim_path_ease_out + +/* Page switching animation type */ +enum class PAGE_ANIM { + /* Default (global) animation type */ + GLOBAL = 0, + + /* No animation */ + NONE, + + /* New page overwrites old page */ + OVER_LEFT, + OVER_RIGHT, + OVER_TOP, + OVER_BOTTOM, + + /* New page pushes old page */ + MOVE_LEFT, + MOVE_RIGHT, + MOVE_TOP, + MOVE_BOTTOM, + + /* The new interface fades in, the old page fades out */ + FADE_ON, +}; + +#endif /* __PAGE_DEF_H */ diff --git a/x_track/src/Frameworks/PageManager/PageFactory.h b/x_track/src/Frameworks/PageManager/PageFactory.h new file mode 100644 index 0000000000000000000000000000000000000000..40f303c9aba6415cfb81a1a805e019ad74c76195 --- /dev/null +++ b/x_track/src/Frameworks/PageManager/PageFactory.h @@ -0,0 +1,44 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __PAGE_FACTORY_H +#define __PAGE_FACTORY_H + +#include "PageBase.h" + +class PageFactory { +public: + PageFactory() {}; + virtual ~PageFactory() {}; + + /** + * @brief Create a page + * @param name Page name + * @return Pointer to the page + */ + virtual PageBase* create(const char* name) + { + return nullptr; + }; +}; + +#endif // !__PAGE_FACTORY_H diff --git a/x_track/src/Frameworks/PageManager/PageLog.cpp b/x_track/src/Frameworks/PageManager/PageLog.cpp new file mode 100644 index 0000000000000000000000000000000000000000..338178fdeb84733d37a4258036e2047cc4be7b9e --- /dev/null +++ b/x_track/src/Frameworks/PageManager/PageLog.cpp @@ -0,0 +1,48 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "PageLog.h" + +#if (PAGE_LOG_LEVEL < PAGE_LOG_LEVEL_OFF) + +#include <stdarg.h> +#include <stdio.h> + +void PageLog(int level, const char* func, const char* format, ...) +{ + if (level < PAGE_LOG_LEVEL) { + return; + } + + va_list args; + va_start(args, format); + + static const char* prefix[] = { "Info", "Warn", "Error" }; + + printf("[PM][%s] %s: ", prefix[level], func); + vprintf(format, args); + printf("\n"); + + va_end(args); +} + +#endif diff --git a/x_track/src/Frameworks/PageManager/PageLog.h b/x_track/src/Frameworks/PageManager/PageLog.h new file mode 100644 index 0000000000000000000000000000000000000000..ee47f2e7bd9818a66eb4bf1dccdf78987862b763 --- /dev/null +++ b/x_track/src/Frameworks/PageManager/PageLog.h @@ -0,0 +1,48 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __PAGE_LOG_H +#define __PAGE_LOG_H + +#define PAGE_LOG_LEVEL_INFO 0 +#define PAGE_LOG_LEVEL_WARN 1 +#define PAGE_LOG_LEVEL_ERROR 2 +#define PAGE_LOG_LEVEL_OFF 3 + +#ifndef PAGE_LOG_LEVEL +#define PAGE_LOG_LEVEL PAGE_LOG_LEVEL_OFF +#endif + +#if (PAGE_LOG_LEVEL < PAGE_LOG_LEVEL_OFF) + +void PageLog(int level, const char* func, const char* format, ...); + +#define PAGE_LOG_INFO(...) PageLog(PAGE_LOG_LEVEL_INFO, __func__, __VA_ARGS__) +#define PAGE_LOG_WARN(...) PageLog(PAGE_LOG_LEVEL_WARN, __func__, __VA_ARGS__) +#define PAGE_LOG_ERROR(...) PageLog(PAGE_LOG_LEVEL_ERROR, __func__, __VA_ARGS__) +#else +#define PAGE_LOG_INFO(...) +#define PAGE_LOG_WARN(...) +#define PAGE_LOG_ERROR(...) +#endif + +#endif diff --git a/x_track/src/Frameworks/PageManager/PageManager.cpp b/x_track/src/Frameworks/PageManager/PageManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..46ca864502929c28fa84a00146f743e0eeb43f9e --- /dev/null +++ b/x_track/src/Frameworks/PageManager/PageManager.cpp @@ -0,0 +1,258 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "PageManager.h" +#include "PageLog.h" +#include <algorithm> +#include <string.h> + +#define PM_EMPTY_PAGE_NAME "EMPTY_PAGE" + +PageManager::PageManager(PageFactory* factory) + : _factory(factory) + , _pageCnt(0) + , _pagePrev(nullptr) + , _pageCurrent(nullptr) + , _animState { 0 } + , _rootParent(nullptr) + , _rootDefaultStyle(nullptr) + , _layerTop(nullptr) + , _eventCallback(nullptr) + , _eventUserData(nullptr) +{ + setGlobalLoadAnim(PAGE_ANIM::OVER_LEFT); +} + +PageManager::~PageManager() +{ + clearStack(); +} + +PageBase* PageManager::findPageInPool(const char* name) +{ + for (auto iter : _pagePool) { + if (strcmp(name, iter->_name) == 0) { + return iter; + } + } + return nullptr; +} + +PageBase* PageManager::findPageInStack(const char* name) +{ + decltype(_pageStack) stk = _pageStack; + while (!stk.empty()) { + PageBase* base = stk.top(); + + if (strcmp(name, base->_name) == 0) { + return base; + } + + stk.pop(); + } + + return nullptr; +} + +PageManager::RES_TYPE PageManager::install(const char* className, const char* appName) +{ + if (!_factory) { + PAGE_LOG_ERROR("Factory was not registered, can't install page"); + return RES_TYPE::ERR_NOT_FOUND; + } + + if (!appName) { + PAGE_LOG_WARN("appName has not set"); + appName = className; + } + + if (findPageInPool(appName) != nullptr) { + PAGE_LOG_ERROR("Page(%s) was registered", appName); + return RES_TYPE::ERR_DUPLICATE; + } + + PageBase* base = _factory->create(className); + if (!base) { + PAGE_LOG_ERROR("Factory has not %s", className); + return RES_TYPE::ERR_UNKNOWN; + } + + base->_id = _pageCnt++; + + PAGE_LOG_INFO("Install Page[class = %s, name = %s]", className, appName); + auto res = registerPage(base, appName); + if (res == RES_TYPE::OK) { + base->onInstalled(); + } + + return res; +} + +PageManager::RES_TYPE PageManager::uninstall(const char* appName) +{ + PAGE_LOG_INFO("Page(%s) uninstall...", appName); + + PageBase* base = findPageInPool(appName); + if (!base) { + PAGE_LOG_ERROR("Page(%s) was not found", appName); + return RES_TYPE::ERR_NOT_FOUND; + } + + auto res = unregisterPage(appName); + if (res != RES_TYPE::OK) { + PAGE_LOG_ERROR("Page(%s) unregister failed", appName); + return res; + } + + if (base->_context.isCached) { + PAGE_LOG_WARN("Page(%s) has cached, unloading...", appName); + base->_context.state = PageBase::STATE::UNLOAD; + stateNext(base); + stateNext(base); + stateNext(base); + stateNext(base); + stateNext(base); + } else { + PAGE_LOG_INFO("Page(%s) has not cache", appName); + } + + delete base; + PAGE_LOG_INFO("Uninstall OK"); + return RES_TYPE::OK; +} + +PageManager::RES_TYPE PageManager::registerPage(PageBase* base, const char* name) +{ + if (findPageInPool(name) != nullptr) { + PAGE_LOG_ERROR("Page(%s) was multi registered", name); + return RES_TYPE::ERR_DUPLICATE; + } + + base->_manager = this; + base->_name = name; + + _pagePool.push_back(base); + + return RES_TYPE::OK; +} + +PageManager::RES_TYPE PageManager::unregisterPage(const char* name) +{ + PAGE_LOG_INFO("Page(%s) unregister...", name); + + PageBase* base = findPageInStack(name); + + if (base != nullptr) { + PAGE_LOG_ERROR("Page(%s) was in stack", name); + return RES_TYPE::ERR_OPERATION; + } + + base = findPageInPool(name); + if (!base) { + PAGE_LOG_ERROR("Page(%s) was not found", name); + return RES_TYPE::ERR_NOT_FOUND; + } + + auto iter = std::find(_pagePool.begin(), _pagePool.end(), base); + + if (iter == _pagePool.end()) { + PAGE_LOG_ERROR("Page(%s) was not found in PagePool", name); + return RES_TYPE::ERR_NOT_FOUND; + } + + _pagePool.erase(iter); + + PAGE_LOG_INFO("Unregister OK"); + return RES_TYPE::OK; +} + +PageBase* PageManager::getStackTop() +{ + return _pageStack.empty() ? nullptr : _pageStack.top(); +} + +PageBase* PageManager::getStackTopAfter() +{ + PageBase* top = getStackTop(); + + if (!top) { + return nullptr; + } + + _pageStack.pop(); + + PageBase* topAfter = getStackTop(); + + _pageStack.push(top); + + return topAfter; +} + +void PageManager::clearStack(bool keepBottom) +{ + while (1) { + PageBase* top = getStackTop(); + + if (!top) { + PAGE_LOG_INFO("Page stack is empty, breaking..."); + break; + } + + PageBase* topAfter = getStackTopAfter(); + + if (!topAfter) { + if (keepBottom) { + _pagePrev = top; + PAGE_LOG_INFO("Keep page stack bottom(%s), breaking...", top->_name); + break; + } else { + _pagePrev = nullptr; + } + } + + fourceUnload(top); + + _pageStack.pop(); + } + PAGE_LOG_INFO("Stack clear done"); +} + +const char* PageManager::getPagePrevName() +{ + return _pagePrev ? _pagePrev->_name : PM_EMPTY_PAGE_NAME; +} + +void PageManager::setEventCallback(EVENT_CALLBACK callback, void* userData) +{ + _eventCallback = callback; + _eventUserData = userData; +} + +PageManager::RES_TYPE PageManager::sendEvent(EVENT_TYPE eventType, void* param) +{ + if (!_eventCallback) { + return RES_TYPE::OK; + } + + EVENT event(eventType, param, _eventUserData); + return _eventCallback(&event); +} diff --git a/x_track/src/Frameworks/PageManager/PageManager.h b/x_track/src/Frameworks/PageManager/PageManager.h new file mode 100644 index 0000000000000000000000000000000000000000..0ca4400bc06063c2da6644922114f605218db1c2 --- /dev/null +++ b/x_track/src/Frameworks/PageManager/PageManager.h @@ -0,0 +1,487 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __PAGE_MANAGER_H +#define __PAGE_MANAGER_H + +#include "PageBase.h" +#include "PageDef.h" +#include "PageFactory.h" +#include <stack> +#include <vector> + +class PageManager { + friend class PageBase; + +public: + enum class EVENT_TYPE { + PAGE_STATE_CHANGED, /* param: PageBase::STATE */ + PAGE_PUSH, /* param: const char* */ + PAGE_POP, /* param: None */ + PAGE_REPLACE, /* param: const char* */ + PAGE_BACK_HOME, /* param: None */ + }; + + enum class RES_TYPE { + OK, + STOP_PROCESS, + ERR_NOT_FOUND, + ERR_DUPLICATE, + ERR_BUSY, + ERR_PARAM, + ERR_OPERATION, + ERR_UNKNOWN, + }; + + struct EVENT { + EVENT(EVENT_TYPE t, void* p, void* u) + : type(t) + , param(p) + , userData(u) + { + } + EVENT_TYPE type; + void* param; + void* userData; + }; + + typedef RES_TYPE (*EVENT_CALLBACK)(EVENT* event); + +public: + PageManager(PageFactory* factory = nullptr); + ~PageManager(); + + /** + * @brief Install the page, and register the page to the page pool + * @param className: The class name of the page + * @param appName: Page application name, no duplicates allowed + * @retval Return true if successful + */ + RES_TYPE install(const char* className, const char* appName); + + /** + * @brief Uninstall page + * @param appName: Page application name, no duplicates allowed + * @retval Return true if the uninstallation is successful + */ + RES_TYPE uninstall(const char* appName); + + /** + * @brief Register the page to the page pool + * @param name: Page application name, duplicate registration is not allowed + * @retval Return true if the registration is successful + */ + RES_TYPE registerPage(PageBase* base, const char* name); + + /** + * @brief Log out the page from the page pool + * @param name: Page application name + * @retval Return true if the logout is successful + */ + RES_TYPE unregisterPage(const char* name); + + /** + * @brief Enter a new page, replace the old page + * @param name: The name of the page to enter + * @param param: Parameters passed to the new page + * @retval Return true if successful + */ + RES_TYPE replace(const char* name, const PageBase::PARAM* param = nullptr); + + /** + * @brief Enter a new page, the old page is pushed onto the stack + * @param name: The name of the page to enter + * @param param: Parameters passed to the new page + * @retval Return true if successful + */ + RES_TYPE push(const char* name, const PageBase::PARAM* param = nullptr); + + /** + * @brief Pop the current page + * @param None + * @retval Return true if successful + */ + RES_TYPE pop(); + + /** + * @brief Back to the main page (the page at the bottom of the stack) + * @param None + * @retval Return true if successful + */ + RES_TYPE backHome(); + + /** + * @brief Get the name of the previous page + * @param None + * @retval The name of the previous page, if it does not exist, return PM_EMPTY_PAGE_NAME + */ + const char* getPagePrevName(); + + /** + * @brief Get page stack depth + * @param None + * @retval The depth of the page stack + */ + size_t getStackDepth() + { + return _pageStack.size(); + } + + /** + * @brief Set global animation properties + * @param anim: Animation type + * @param time: Animation duration + * @param path: Animation curve + * @retval None + */ + void setGlobalLoadAnim( + PAGE_ANIM anim, + uint32_t time = PAGE_ANIM_TIME_DEFAULT, + lv_anim_path_cb_t path = PAGE_ANIM_PATH_DEFAULT); + + /** + * @brief Set the root parent of the page + * @param par: The parent of the root, if it is nullptr, the parent is lv_scr_act() + * @retval None + */ + void setRootParent(lv_obj_t* par) + { + _rootParent = par; + } + + /** + * @brief Get the root parent of the page + * @param None + * @retval The parent of the root + */ + lv_obj_t* getRootParent() + { + return (_rootParent ? _rootParent : lv_scr_act()); + } + + /** + * @brief Set the default style of the root + * @param style: The default style of the root + * @retval None + */ + void setRootDefaultStyle(lv_style_t* style) + { + _rootDefaultStyle = style; + } + + /** + * @brief Set the top layer of the page + * @param obj: The top layer of the page + * @retval None + */ + void setLayerTop(lv_obj_t* obj) + { + _layerTop = obj; + } + + /** + * @brief Get the top layer of the page + * @param None + * @retval The top layer of the page + */ + lv_obj_t* getLayerTop() + { + return _layerTop ? _layerTop : lv_layer_top(); + } + + /** + * @brief Set the event callback + * @param callback: Event callback + * @param userData: User data + * @retval None + */ + void setEventCallback(EVENT_CALLBACK callback, void* userData); + +private: + /* Animation switching param */ + struct ANIM_PARAM { + ANIM_PARAM() + : enter { 0 } + , exit { 0 } + + { + } + /* As the entered party */ + struct + { + int32_t start; + int32_t end; + } enter; + + /* As the exited party */ + struct + { + int32_t start; + int32_t end; + } exit; + }; + + /* Page loading animation properties */ + struct LOAD_ANIM_ATTR { + LOAD_ANIM_ATTR() + : setter(nullptr) + , getter(nullptr) + { + } + void (*setter)(void*, int32_t); + int32_t (*getter)(void*); + ANIM_PARAM push; + ANIM_PARAM pop; + }; + +private: + /* Page factory */ + PageFactory* _factory; + + /* Page count */ + uint16_t _pageCnt; + + /* Page pool */ + std::vector<PageBase*> _pagePool; + + /* Page stack */ + std::stack<PageBase*> _pageStack; + + /* Previous page */ + PageBase* _pagePrev; + + /* The current page */ + PageBase* _pageCurrent; + + /* Page animation status */ + struct + { + bool isSwitchReq; // Has switch request + bool isBusy; // Is switching + bool isEntering; // Is in entering action + + PageBase::ANIM_ATTR current; // Current animation properties + PageBase::ANIM_ATTR global; // global animation properties + } _animState; + + /* Root */ + lv_obj_t* _rootParent; + lv_style_t* _rootDefaultStyle; + + /* Top layer */ + lv_obj_t* _layerTop; + + /* Event */ + EVENT_CALLBACK _eventCallback; + void* _eventUserData; + +private: + /** + * @brief Search pages in the page pool + * @param name: Page name + * @retval A pointer to the base class of the page, or nullptr if not found + */ + PageBase* findPageInPool(const char* name); + + /** + * @brief Search pages in the page stack + * @param name: Page name + * @retval A pointer to the base class of the page, or nullptr if not found + */ + PageBase* findPageInStack(const char* name); + + /** + * @brief Get the top page of the page stack + * @param None + * @retval A pointer to the base class of the page + */ + PageBase* getStackTop(); + + /** + * @brief Get the page below the top of the page stack + * @param None + * @retval A pointer to the base class of the page + */ + PageBase* getStackTopAfter(); + + /** + * @brief Clear the page stack and end the life cycle of all pages in the page stack + * @param keepBottom: Whether to keep the bottom page of the stack + * @retval None + */ + void clearStack(bool keepBottom = false); + + /** + * @brief Force the end of the life cycle of the page without animation + * @param base: Pointer to the page being executed + * @retval Return true if successful + */ + RES_TYPE fourceUnload(PageBase* base); + + /** + * @brief Get page loading animation properties + * @param anim: Animation type + * @param attr: Pointer to attribute + * @retval Whether the acquisition is successful + */ + RES_TYPE getLoadAnimAttr(PAGE_ANIM anim, LOAD_ANIM_ATTR* attr); + + /** + * @brief Set animation default parameters + * @param a: Pointer to animation + * @retval None + */ + void animDefaultInit(lv_anim_t* a); + + /** + * @brief Get the current page loading animation properties + * @param attr: Pointer to attribute + * @retval Whether the acquisition is successful + */ + RES_TYPE getCurrentLoadAnimAttr(LOAD_ANIM_ATTR* attr) + { + return getLoadAnimAttr(getCurrentLoadAnimType(), attr); + } + + /** + * @brief Get the current page loading animation type + * @param None + * @retval The current page loading animation type + */ + PAGE_ANIM getCurrentLoadAnimType() + { + return _animState.current.type; + } + + /** + * @brief Page switching + * @param newNode: Pointer to new page + * @param isEnterAct: Whether it is a ENTER action + * @param param: Parameters passed to the new page + * @retval Return true if successful + */ + RES_TYPE switchTo(PageBase* base, bool isEnterAct, const PageBase::PARAM* param = nullptr); + + /** + * @brief PPage switching animation execution end callback + * @param a: Pointer to animation + * @retval None + */ + static void onSwitchAnimFinish(lv_anim_t* a); + + /** + * @brief Create page switching animation + * @param a: Point to the animated page + * @retval None + */ + void switchAnimCreate(PageBase* base); + + /** + * @brief Update current animation properties, apply page custom animation + * @param base: Pointer to page + * @retval None + */ + void switchAnimTypeUpdate(PageBase* base); + + /** + * @brief Page switching request check + * @param None + * @retval Return true if all pages are executed + */ + RES_TYPE switchReqCheck(); + + /** + * @brief Check if the page switching animation is being executed + * @param None + * @retval Return true if it is executing + */ + RES_TYPE switchAnimStateCheck(); + + /** + * @brief Page loading status + * @param base: Pointer to the updated page + * @retval Next state + */ + PageBase::STATE stateLoad(PageBase* base); + + /** + * @brief The page is about to show the status + * @param base: Pointer to the updated page + * @retval Next state + */ + PageBase::STATE stateWillAppear(PageBase* base); + + /** + * @brief The status of the page display + * @param base: Pointer to the updated page + * @retval Next state + */ + PageBase::STATE stateDidAppear(PageBase* base); + + /** + * @brief The page is about to disappear + * @param base: Pointer to the updated page + * @retval Next state + */ + PageBase::STATE stateWillDisappear(PageBase* base); + + /** + * @brief Page disappeared end state + * @param base: Pointer to the updated page + * @retval Next state + */ + PageBase::STATE stateDidDisappear(PageBase* base); + + /** + * @brief Page unload complete + * @param base: Pointer to the updated page + * @retval Next state + */ + PageBase::STATE stateUnload(PageBase* base); + + /** + * @brief Update page state machine + * @param base: Pointer to the updated page + * @retval None + */ + void stateNext(PageBase* base); + + /** + * @brief Get the current state of the page + * @param None + * @retval The current state of the page + */ + PageBase::STATE getState() + { + return _pageCurrent->_context.state; + } + + /** + * @brief Send event + * @param eventType: Event type + * @param param: Event parameters + * @retval None + */ + RES_TYPE sendEvent(EVENT_TYPE eventType, void* param = nullptr); +}; + +#endif /* __PAGE_BASE_H */ diff --git a/x_track/src/Frameworks/PageManager/PageManagerAnim.cpp b/x_track/src/Frameworks/PageManager/PageManagerAnim.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4429e675bfb650b90ff8d6461b403b83f6bfc5b3 --- /dev/null +++ b/x_track/src/Frameworks/PageManager/PageManagerAnim.cpp @@ -0,0 +1,191 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "PageLog.h" +#include "PageManager.h" + +PageManager::RES_TYPE PageManager::getLoadAnimAttr(PAGE_ANIM anim, LOAD_ANIM_ATTR* attr) +{ + lv_obj_t* rootParent = getRootParent(); + lv_obj_update_layout(rootParent); + lv_coord_t hor = lv_obj_get_width(rootParent); + lv_coord_t ver = lv_obj_get_height(rootParent); + + switch (anim) { + case PAGE_ANIM::OVER_LEFT: + attr->push.enter.start = hor; + attr->push.enter.end = 0; + attr->push.exit.start = 0; + attr->push.exit.end = 0; + + attr->pop.enter.start = 0; + attr->pop.enter.end = 0; + attr->pop.exit.start = 0; + attr->pop.exit.end = hor; + break; + + case PAGE_ANIM::OVER_RIGHT: + attr->push.enter.start = -hor; + attr->push.enter.end = 0; + attr->push.exit.start = 0; + attr->push.exit.end = 0; + + attr->pop.enter.start = 0; + attr->pop.enter.end = 0; + attr->pop.exit.start = 0; + attr->pop.exit.end = -hor; + break; + + case PAGE_ANIM::OVER_TOP: + attr->push.enter.start = ver; + attr->push.enter.end = 0; + attr->push.exit.start = 0; + attr->push.exit.end = 0; + + attr->pop.enter.start = 0; + attr->pop.enter.end = 0; + attr->pop.exit.start = 0; + attr->pop.exit.end = ver; + break; + + case PAGE_ANIM::OVER_BOTTOM: + attr->push.enter.start = -ver; + attr->push.enter.end = 0; + attr->push.exit.start = 0; + attr->push.exit.end = 0; + + attr->pop.enter.start = 0; + attr->pop.enter.end = 0; + attr->pop.exit.start = 0; + attr->pop.exit.end = -ver; + break; + + case PAGE_ANIM::MOVE_LEFT: + attr->push.enter.start = hor; + attr->push.enter.end = 0; + attr->push.exit.start = 0; + attr->push.exit.end = -hor; + + attr->pop.enter.start = -hor; + attr->pop.enter.end = 0; + attr->pop.exit.start = 0; + attr->pop.exit.end = hor; + break; + + case PAGE_ANIM::MOVE_RIGHT: + attr->push.enter.start = -hor; + attr->push.enter.end = 0; + attr->push.exit.start = 0; + attr->push.exit.end = hor; + + attr->pop.enter.start = hor; + attr->pop.enter.end = 0; + attr->pop.exit.start = 0; + attr->pop.exit.end = -hor; + break; + + case PAGE_ANIM::MOVE_TOP: + attr->push.enter.start = ver; + attr->push.enter.end = 0; + attr->push.exit.start = 0; + attr->push.exit.end = -ver; + + attr->pop.enter.start = -ver; + attr->pop.enter.end = 0; + attr->pop.exit.start = 0; + attr->pop.exit.end = ver; + break; + + case PAGE_ANIM::MOVE_BOTTOM: + attr->push.enter.start = -ver; + attr->push.enter.end = 0; + attr->push.exit.start = 0; + attr->push.exit.end = ver; + + attr->pop.enter.start = ver; + attr->pop.enter.end = 0; + attr->pop.exit.start = 0; + attr->pop.exit.end = -ver; + break; + + case PAGE_ANIM::FADE_ON: + attr->push.enter.start = LV_OPA_TRANSP; + attr->push.enter.end = LV_OPA_COVER; + attr->push.exit.start = LV_OPA_COVER; + attr->push.exit.end = LV_OPA_COVER; + + attr->pop.enter.start = LV_OPA_COVER; + attr->pop.enter.end = LV_OPA_COVER; + attr->pop.exit.start = LV_OPA_COVER; + attr->pop.exit.end = LV_OPA_TRANSP; + break; + + case PAGE_ANIM::NONE: + /* Use default NONE */ + break; + + default: + PAGE_LOG_ERROR("Load anim type error: %d", anim); + return RES_TYPE::ERR_PARAM; + } + + /* Determine the setter and getter of the animation */ + switch (anim) { + case PAGE_ANIM::OVER_LEFT: + case PAGE_ANIM::OVER_RIGHT: + case PAGE_ANIM::MOVE_LEFT: + case PAGE_ANIM::MOVE_RIGHT: + attr->setter = [](void* obj, int32_t v) { + lv_obj_set_x((lv_obj_t*)obj, v); + }; + attr->getter = [](void* obj) { + return (int32_t)lv_obj_get_x((lv_obj_t*)obj); + }; + break; + + case PAGE_ANIM::OVER_TOP: + case PAGE_ANIM::OVER_BOTTOM: + case PAGE_ANIM::MOVE_TOP: + case PAGE_ANIM::MOVE_BOTTOM: + attr->setter = [](void* obj, int32_t v) { + lv_obj_set_y((lv_obj_t*)obj, v); + }; + attr->getter = [](void* obj) { + return (int32_t)lv_obj_get_y((lv_obj_t*)obj); + }; + break; + + case PAGE_ANIM::FADE_ON: + attr->setter = [](void* obj, int32_t v) { + lv_obj_set_style_opa((lv_obj_t*)obj, (lv_opa_t)v, LV_PART_MAIN); + }; + attr->getter = [](void* obj) { + return (int32_t)lv_obj_get_style_opa((lv_obj_t*)obj, LV_PART_MAIN); + }; + break; + + default: + break; + } + + return RES_TYPE::OK; +} diff --git a/x_track/src/Frameworks/PageManager/PageManagerRouter.cpp b/x_track/src/Frameworks/PageManager/PageManagerRouter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6a8ecac23dde55224c84d1f8a130ceefbc351cd0 --- /dev/null +++ b/x_track/src/Frameworks/PageManager/PageManagerRouter.cpp @@ -0,0 +1,447 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "PageLog.h" +#include "PageManager.h" +#include <string.h> + +PageManager::RES_TYPE PageManager::replace(const char* name, const PageBase::PARAM* param) +{ + /* Precheck event hook */ + auto res = sendEvent(EVENT_TYPE::PAGE_REPLACE, (void*)name); + if (res != RES_TYPE::OK) { + return res; + } + + /* Check whether the animation of switching pages is being executed */ + res = switchAnimStateCheck(); + if (res != RES_TYPE::OK) { + return res; + } + + /* Check whether the stack is repeatedly pushed */ + if (findPageInStack(name) != nullptr) { + PAGE_LOG_ERROR("Page(%s) was multi push", name); + return RES_TYPE::ERR_DUPLICATE; + } + + /* Check if the page is registered in the page pool */ + PageBase* base = findPageInPool(name); + + if (!base) { + PAGE_LOG_ERROR("Page(%s) was not install", name); + return RES_TYPE::ERR_NOT_FOUND; + } + + /* Get the top page of the stack */ + PageBase* top = getStackTop(); + + if (!top) { + PAGE_LOG_ERROR("Stack top is NULL"); + return RES_TYPE::ERR_OPERATION; + } + + /* Force disable cache */ + top->_context.isCached = false; + + /* Synchronous automatic cache configuration */ + base->_context.isDisableAutoCache = base->_context.reqDisableAutoCache; + + /* Remove current page */ + _pageStack.pop(); + + /* Push into the stack */ + _pageStack.push(base); + + PAGE_LOG_INFO("Page(%s) replace Page(%s) (param = %p)", name, top->_name, param); + + /* Page switching execution */ + return switchTo(base, true, param); +} + +PageManager::RES_TYPE PageManager::push(const char* name, const PageBase::PARAM* param) +{ + /* Precheck event hook */ + auto res = sendEvent(EVENT_TYPE::PAGE_PUSH, (void*)name); + if (res != RES_TYPE::OK) { + return res; + } + + /* Check whether the animation of switching pages is being executed */ + res = switchAnimStateCheck(); + if (res != RES_TYPE::OK) { + return res; + } + + /* Check whether the stack is repeatedly pushed */ + if (findPageInStack(name) != nullptr) { + PAGE_LOG_ERROR("Page(%s) was multi push", name); + return RES_TYPE::ERR_DUPLICATE; + } + + /* Check if the page is registered in the page pool */ + PageBase* base = findPageInPool(name); + + if (!base) { + PAGE_LOG_ERROR("Page(%s) was not install", name); + return RES_TYPE::ERR_NOT_FOUND; + } + + /* Synchronous automatic cache configuration */ + base->_context.isDisableAutoCache = base->_context.reqDisableAutoCache; + + /* Push into the stack */ + _pageStack.push(base); + + PAGE_LOG_INFO("Page(%s) push >> [Screen] (param = %p)", name, param); + + /* Page switching execution */ + return switchTo(base, true, param); +} + +PageManager::RES_TYPE PageManager::pop() +{ + /* Precheck event hook */ + auto res = sendEvent(EVENT_TYPE::PAGE_POP); + if (res != RES_TYPE::OK) { + return res; + } + + /* Check whether the animation of switching pages is being executed */ + res = switchAnimStateCheck(); + if (res != RES_TYPE::OK) { + return res; + } + + if (_pageStack.size() <= 1) { + PAGE_LOG_WARN("Bottom of stack, cat't pop"); + return RES_TYPE::ERR_OPERATION; + } + + /* Get the top page of the stack */ + PageBase* top = getStackTop(); + LV_ASSERT_NULL(top); + if (!top) { + PAGE_LOG_ERROR("Stack top is NULL"); + return RES_TYPE::ERR_OPERATION; + } + + /* Whether to turn off automatic cache */ + if (!top->_context.isDisableAutoCache) { + PAGE_LOG_INFO("Page(%s) has auto cache, cache disabled", top->_name); + top->_context.isCached = false; + } + + PAGE_LOG_INFO("Page(%s) pop << [Screen]", top->_name); + + /* Page popup */ + _pageStack.pop(); + + /* Get the next page */ + top = getStackTop(); + + /* Page switching execution */ + return switchTo(top, false, nullptr); +} + +PageManager::RES_TYPE PageManager::switchTo(PageBase* newNode, bool isEnterAct, const PageBase::PARAM* param) +{ + if (!newNode) { + PAGE_LOG_ERROR("newNode is nullptr"); + return RES_TYPE::ERR_PARAM; + } + + /* Whether page switching has been requested */ + if (_animState.isSwitchReq) { + PAGE_LOG_WARN("Page switch busy, reqire(%s) is ignore", newNode->_name); + return RES_TYPE::ERR_BUSY; + } + + _animState.isSwitchReq = true; + + /* Is there a parameter to pass */ + if (param != nullptr) { + PAGE_LOG_INFO("param is detect, %s >> param(%p) >> %s", getPagePrevName(), param, newNode->_name); + + void* buffer = nullptr; + + if (!newNode->_context.param.ptr) { + buffer = lv_malloc(param->size); + LV_ASSERT_MALLOC(buffer); + if (!buffer) { + PAGE_LOG_ERROR("param malloc failed"); + } else { + PAGE_LOG_INFO("param(%p) malloc[%d]", buffer, param->size); + } + } else if (newNode->_context.param.size == param->size) { + buffer = newNode->_context.param.ptr; + PAGE_LOG_INFO("param(%p) is exist", buffer); + } + + if (buffer != nullptr) { + memcpy(buffer, param->ptr, param->size); + PAGE_LOG_INFO("param memcpy[%d] %p >> %p", param->size, param->ptr, buffer); + newNode->_context.param.ptr = buffer; + newNode->_context.param.size = param->size; + } + } + + /* Record current page */ + _pageCurrent = newNode; + + /* If the current page has a cache */ + if (_pageCurrent->_context.isCached) { + /* Direct display, no need to load */ + PAGE_LOG_INFO("Page(%s) has cached, appear driectly", _pageCurrent->_name); + _pageCurrent->_context.state = PageBase::STATE::WILL_APPEAR; + } else { + /* Load page */ + _pageCurrent->_context.state = PageBase::STATE::LOAD; + } + + if (_pagePrev != nullptr) { + _pagePrev->_context.anim.isEnter = false; + } + + _pageCurrent->_context.anim.isEnter = true; + + _animState.isEntering = isEnterAct; + + if (_animState.isEntering) { + /* Update the animation configuration according to the current page */ + switchAnimTypeUpdate(_pageCurrent); + } + + /* Update the state machine of the previous page */ + stateNext(_pagePrev); + + /* Update the state machine of the current page */ + stateNext(_pageCurrent); + + /* Since page creation takes a certain amount of time, + * the animation is created uniformly after the state machine is executed, + * and the animation start and end times are aligned. + */ + switchAnimCreate(_pagePrev); + switchAnimCreate(_pageCurrent); + + /* Move the layer, move the new page to the front */ + if (_animState.isEntering) { + PAGE_LOG_INFO("Page ENTER is detect, move Page(%s) to foreground", _pageCurrent->_name); + if (_pagePrev) + lv_obj_move_foreground(_pagePrev->_root); + lv_obj_move_foreground(_pageCurrent->_root); + } else { + PAGE_LOG_INFO("Page EXIT is detect, move Page(%s) to foreground", getPagePrevName()); + lv_obj_move_foreground(_pageCurrent->_root); + if (_pagePrev) + lv_obj_move_foreground(_pagePrev->_root); + } + return RES_TYPE::OK; +} + +PageManager::RES_TYPE PageManager::fourceUnload(PageBase* base) +{ + if (!base) { + PAGE_LOG_ERROR("Page is nullptr, Unload failed"); + return RES_TYPE::ERR_PARAM; + } + + PAGE_LOG_INFO("Page(%s) Fource unloading...", base->_name); + + if (base->_context.state == PageBase::STATE::ACTIVITY) { + PAGE_LOG_INFO("Page state is ACTIVITY, Disappearing..."); + base->onViewWillDisappear(); + base->onViewDidDisappear(); + } + + base->_context.state = stateUnload(base); + + return RES_TYPE::OK; +} + +PageManager::RES_TYPE PageManager::backHome() +{ + /* Precheck event hook */ + auto res = sendEvent(EVENT_TYPE::PAGE_BACK_HOME); + if (res != RES_TYPE::OK) { + return res; + } + + /* Check whether the animation of switching pages is being executed */ + res = switchAnimStateCheck(); + if (res != RES_TYPE::OK) { + return res; + } + + clearStack(true); + + _pagePrev = nullptr; + + PageBase* home = getStackTop(); + + return switchTo(home, false); +} + +PageManager::RES_TYPE PageManager::switchAnimStateCheck() +{ + if (_animState.isSwitchReq || _animState.isBusy) { + PAGE_LOG_WARN( + "Page switch busy[AnimState.isSwitchReq = %d," + "AnimState.isBusy = %d]," + "request ignored", + _animState.isSwitchReq, + _animState.isBusy); + return RES_TYPE::ERR_BUSY; + } + + return RES_TYPE::OK; +} + +PageManager::RES_TYPE PageManager::switchReqCheck() +{ + auto res = RES_TYPE::ERR_BUSY; + bool lastNodeBusy = _pagePrev && _pagePrev->_context.anim.isBusy; + + if (!_pageCurrent->_context.anim.isBusy && !lastNodeBusy) { + PAGE_LOG_INFO("----Page switch was all finished----"); + _animState.isSwitchReq = false; + res = RES_TYPE::OK; + _pagePrev = _pageCurrent; + } else { + if (_pageCurrent->_context.anim.isBusy) { + PAGE_LOG_WARN("Page PageCurrent(%s) is busy", _pageCurrent->_name); + } else { + PAGE_LOG_WARN("Page PagePrev(%s) is busy", getPagePrevName()); + } + } + + return res; +} + +void PageManager::onSwitchAnimFinish(lv_anim_t* a) +{ + auto base = (PageBase*)lv_anim_get_user_data(a); + auto manager = base->_manager; + + PAGE_LOG_INFO("Page(%s) anim finish", base->_name); + + manager->stateNext(base); + base->_context.anim.isBusy = false; + auto res = manager->switchReqCheck(); + + if (!manager->_animState.isEntering && res == RES_TYPE::OK) { + manager->switchAnimTypeUpdate(manager->_pageCurrent); + } +} + +void PageManager::switchAnimCreate(PageBase* base) +{ + if (!base) { + return; + } + + LOAD_ANIM_ATTR animAttr; + if (getCurrentLoadAnimAttr(&animAttr) != RES_TYPE::OK) { + return; + } + + lv_anim_t a; + animDefaultInit(&a); + lv_anim_set_user_data(&a, base); + lv_anim_set_var(&a, base->_root); + lv_anim_set_ready_cb(&a, onSwitchAnimFinish); + lv_anim_set_exec_cb(&a, animAttr.setter); + + int32_t start = 0; + + if (animAttr.getter) { + start = animAttr.getter(base->_root); + } + + if (_animState.isEntering) { + if (base->_context.anim.isEnter) { + lv_anim_set_values( + &a, + animAttr.push.enter.start, + animAttr.push.enter.end); + } else /* Exit */ + { + lv_anim_set_values( + &a, + start, + animAttr.push.exit.end); + } + } else /* Pop */ + { + if (base->_context.anim.isEnter) { + lv_anim_set_values( + &a, + animAttr.pop.enter.start, + animAttr.pop.enter.end); + } else /* Exit */ + { + lv_anim_set_values( + &a, + start, + animAttr.pop.exit.end); + } + } + + lv_anim_start(&a); + base->_context.anim.isBusy = true; +} + +void PageManager::setGlobalLoadAnim(PAGE_ANIM anim, uint32_t time, lv_anim_path_cb_t path) +{ + _animState.global.type = anim; + _animState.global.duration = time; + _animState.global.path = path; + + PAGE_LOG_INFO("Set global load anim type = %d", anim); +} + +void PageManager::switchAnimTypeUpdate(PageBase* base) +{ + if (base->_context.anim.attr.type == PAGE_ANIM::GLOBAL) { + PAGE_LOG_INFO( + "Page(%s) anim.type was not set, use AnimState.global.type = %d", + base->_name, + _animState.global.type); + _animState.current = _animState.global; + } else { + PAGE_LOG_INFO( + "Page(%s) custom anim.type set = %d", + base->_name, + base->_context.anim.attr.type); + _animState.current = base->_context.anim.attr; + } +} + +void PageManager::animDefaultInit(lv_anim_t* a) +{ + lv_anim_init(a); + + uint32_t time = (getCurrentLoadAnimType() == PAGE_ANIM::NONE) ? 0 : _animState.current.duration; + lv_anim_set_time(a, time); + lv_anim_set_path_cb(a, _animState.current.path); +} diff --git a/x_track/src/Frameworks/PageManager/PageManagerState.cpp b/x_track/src/Frameworks/PageManager/PageManagerState.cpp new file mode 100644 index 0000000000000000000000000000000000000000..154edd8223916544a511279821c45726dec649e0 --- /dev/null +++ b/x_track/src/Frameworks/PageManager/PageManagerState.cpp @@ -0,0 +1,190 @@ +/* + * MIT License + * Copyright (c) 2021 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "PageLog.h" +#include "PageManager.h" + +void PageManager::stateNext(PageBase* base) +{ + if (!base) { + return; + } + + sendEvent(EVENT_TYPE::PAGE_STATE_CHANGED, (void*)&base->_context.state); + + switch (base->_context.state) { + case PageBase::STATE::IDLE: + PAGE_LOG_INFO("Page(%s) state idle", base->_name); + break; + + case PageBase::STATE::LOAD: + base->_context.state = stateLoad(base); + stateNext(base); + break; + + case PageBase::STATE::WILL_APPEAR: + base->_context.state = stateWillAppear(base); + break; + + case PageBase::STATE::DID_APPEAR: + base->_context.state = stateDidAppear(base); + PAGE_LOG_INFO("Page(%s) state active", base->_name); + break; + + case PageBase::STATE::ACTIVITY: + PAGE_LOG_INFO("Page(%s) state active break", base->_name); + base->_context.state = PageBase::STATE::WILL_DISAPPEAR; + stateNext(base); + break; + + case PageBase::STATE::WILL_DISAPPEAR: + base->_context.state = stateWillDisappear(base); + break; + + case PageBase::STATE::DID_DISAPPEAR: + base->_context.state = stateDidDisappear(base); + if (base->_context.state == PageBase::STATE::UNLOAD) { + stateNext(base); + } + break; + + case PageBase::STATE::UNLOAD: + base->_context.state = stateUnload(base); + break; + + default: + PAGE_LOG_ERROR("Page(%s) state[%d] was NOT FOUND!", base->_name, base->_context.state); + break; + } +} + +PageBase::STATE PageManager::stateLoad(PageBase* base) +{ + PAGE_LOG_INFO("Page(%s) state load", base->_name); + + if (base->_root != nullptr) { + PAGE_LOG_ERROR("Page(%s) root must be nullptr", base->_name); + } + + lv_obj_t* parent = base->_manager->getRootParent(); + lv_obj_t* root_obj = lv_obj_create(parent); + + lv_obj_clear_flag(root_obj, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_user_data(root_obj, base); + + if (base->_context.backGestureDir != LV_DIR_NONE) { + lv_obj_remove_flag(root_obj, LV_OBJ_FLAG_GESTURE_BUBBLE); + lv_obj_add_event_cb( + root_obj, + [](lv_event_t* e) { + auto self = (PageBase*)lv_event_get_user_data(e); + auto dir = lv_indev_get_gesture_dir(lv_indev_active()); + if (dir == self->_context.backGestureDir) { + self->getManager()->pop(); + } + }, + LV_EVENT_GESTURE, + base); + } + + if (_rootDefaultStyle) { + lv_obj_add_style(root_obj, _rootDefaultStyle, LV_PART_MAIN); + } + + base->_root = root_obj; + base->onViewLoad(); + + base->onViewDidLoad(); + + if (base->_context.isDisableAutoCache) { + PAGE_LOG_INFO("Page(%s) disable auto cache, reqEnableCache = %d", base->_name, base->_context.reqEnableCache); + base->_context.isCached = base->_context.reqEnableCache; + } else { + PAGE_LOG_INFO("Page(%s) AUTO cached", base->_name); + base->_context.isCached = true; + } + + return PageBase::STATE::WILL_APPEAR; +} + +PageBase::STATE PageManager::stateWillAppear(PageBase* base) +{ + PAGE_LOG_INFO("Page(%s) state will appear", base->_name); + base->onViewWillAppear(); + lv_obj_clear_flag(base->_root, LV_OBJ_FLAG_HIDDEN); + return PageBase::STATE::DID_APPEAR; +} + +PageBase::STATE PageManager::stateDidAppear(PageBase* base) +{ + PAGE_LOG_INFO("Page(%s) state did appear", base->_name); + base->onViewDidAppear(); + return PageBase::STATE::ACTIVITY; +} + +PageBase::STATE PageManager::stateWillDisappear(PageBase* base) +{ + PAGE_LOG_INFO("Page(%s) state will disappear", base->_name); + base->onViewWillDisappear(); + return PageBase::STATE::DID_DISAPPEAR; +} + +PageBase::STATE PageManager::stateDidDisappear(PageBase* base) +{ + PAGE_LOG_INFO("Page(%s) state did disappear", base->_name); + lv_obj_add_flag(base->_root, LV_OBJ_FLAG_HIDDEN); + base->onViewDidDisappear(); + if (base->_context.isCached) { + PAGE_LOG_INFO("Page(%s) has cached", base->_name); + return PageBase::STATE::WILL_APPEAR; + } else { + return PageBase::STATE::UNLOAD; + } +} + +PageBase::STATE PageManager::stateUnload(PageBase* base) +{ + PAGE_LOG_INFO("Page(%s) state unload", base->_name); + if (!base->_root) { + PAGE_LOG_WARN("Page is unloaded!"); + return PageBase::STATE::IDLE; + } + + base->onViewUnload(); + if (base->_context.param.ptr != nullptr && base->_context.param.size != 0) { + PAGE_LOG_INFO("Page(%s) free param(%p)[%d]", base->_name, base->_context.param.ptr, base->_context.param.size); + lv_free(base->_context.param.ptr); + base->_context.param.ptr = nullptr; + base->_context.param.size = 0; + } + + /* Prevent another timer from modifying the style during async deletion */ + lv_obj_clean(base->_root); + lv_obj_remove_style_all(base->_root); + + /* Delete after the end of the root animation life cycle */ + lv_obj_del_async(base->_root); + base->_root = nullptr; + base->_context.isCached = false; + base->onViewDidUnload(); + return PageBase::STATE::IDLE; +} diff --git a/x_track/src/Frameworks/ResourceManager/RM_Log.h b/x_track/src/Frameworks/ResourceManager/RM_Log.h new file mode 100644 index 0000000000000000000000000000000000000000..39052383257a95952e5deb79ec74b800171dd5c3 --- /dev/null +++ b/x_track/src/Frameworks/ResourceManager/RM_Log.h @@ -0,0 +1,40 @@ +/* + * MIT License + * Copyright (c) 2021 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __RM_LOG_H +#define __RM_LOG_H + +#define RESOURCE_MANAGER_USE_LOG 1 + +#if !defined(ARDUINO) && RESOURCE_MANAGER_USE_LOG +#include <stdio.h> +# define _RM_LOG(format, ...) printf("[RM]" format "\r\n", ##__VA_ARGS__) +# define RM_LOG_INFO(format, ...) //_RM_LOG("[Info] " format, ##__VA_ARGS__) +# define RM_LOG_WARN(format, ...) _RM_LOG("[Warn] " format, ##__VA_ARGS__) +# define RM_LOG_ERROR(format, ...) _RM_LOG("[Error] " format, ##__VA_ARGS__) +#else +# define RM_LOG_INFO(...) +# define RM_LOG_WARN(...) +# define RM_LOG_ERROR(...) +#endif + +#endif diff --git a/x_track/src/Frameworks/ResourceManager/ResourceManager.h b/x_track/src/Frameworks/ResourceManager/ResourceManager.h new file mode 100644 index 0000000000000000000000000000000000000000..ec047e9bf9f614f6008623c457f491963ea5a646 --- /dev/null +++ b/x_track/src/Frameworks/ResourceManager/ResourceManager.h @@ -0,0 +1,86 @@ +/* + * MIT License + * Copyright (c) 2021 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __RESOURCE_MANAGER_H +#define __RESOURCE_MANAGER_H + +#include <stddef.h> + +template <typename KEY_TYPE, typename VALUE_TYPE> +class ResourceManager { +public: + typedef struct { + KEY_TYPE key; + VALUE_TYPE value; + } KEY_PAIR; + + typedef bool (*COMPARE_CALLBACK)(KEY_TYPE key1, KEY_TYPE key2); + +public: + ResourceManager(KEY_PAIR* array, size_t size, COMPARE_CALLBACK compareCallback, VALUE_TYPE defaultValue) + : _array(array) + , _size(size) + , _compareCallback(compareCallback) + , _defaultValue(defaultValue) + { + } + ~ResourceManager() { } + + VALUE_TYPE* get(KEY_TYPE key) + { + for (size_t i = 0; i < _size; i++) { + if (_compareCallback(key, _array[i].key)) { + return &_array[i].value; + } + } + return &_defaultValue; + } + + bool set(KEY_TYPE key, VALUE_TYPE value) + { + for (size_t i = 0; i < _size; i++) { + if (_compareCallback(key, _array[i].key)) { + _array[i].value = value; + return true; + } + } + return false; + } + + void setDefault(VALUE_TYPE value) + { + _defaultValue = value; + } + + VALUE_TYPE getDefault() + { + return _defaultValue; + } + +private: + KEY_PAIR* _array; + size_t _size; + COMPARE_CALLBACK _compareCallback; + VALUE_TYPE _defaultValue; +}; + +#endif diff --git a/x_track/src/Frameworks/ResourceManager/ResourceManagerStatic.cpp b/x_track/src/Frameworks/ResourceManager/ResourceManagerStatic.cpp new file mode 100644 index 0000000000000000000000000000000000000000..87aa7e4f699fa39639d769298589e76d22a06b84 --- /dev/null +++ b/x_track/src/Frameworks/ResourceManager/ResourceManagerStatic.cpp @@ -0,0 +1,43 @@ +/* + * Created by W-Mai on 2022/10/17. + * + * MIT License + * Copyright (c) 2022 XCLZ STUDIO + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "ResourceManagerStatic.h" +#include <algorithm> +#include <cstring> + +template <typename KeyType, typename ValType, size_t NUM> +const ValType& ResourceManagerStatic<KeyType, ValType, NUM>::get(KeyType key) +{ + using KeyValType = std::pair<KeyType, ValType>; + auto res = std::find_if(__pool_, __pool_ + NUM, [&key](KeyValType node) { + return strcmp(node.first, key) == 0; + }); + + if (res == __pool_ + NUM) { + return __defaultValue_; + } else { + return res->second; + } +} diff --git a/x_track/src/Frameworks/ResourceManager/ResourceManagerStatic.h b/x_track/src/Frameworks/ResourceManager/ResourceManagerStatic.h new file mode 100644 index 0000000000000000000000000000000000000000..3976eb70377fb330ef911dd4dd1cac4c82e1b796 --- /dev/null +++ b/x_track/src/Frameworks/ResourceManager/ResourceManagerStatic.h @@ -0,0 +1,76 @@ +/* + * Created by W-Mai on 2022/10/15. + * + * MIT License + * Copyright (c) 2022 XCLZ STUDIO + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __RESOURCE_MANAGER_STATIC_H +#define __RESOURCE_MANAGER_STATIC_H + +#include <utility> +#include <vector> +#include <cstddef> + +template <typename KeyType, typename ValType, size_t NUM> +class ResourceManagerStatic { +public: + using KeyValueType = std::pair<KeyType, ValType>; + +private: + KeyValueType* __pool_; + + ValType __defaultValue_; + +public: + explicit ResourceManagerStatic(KeyValueType* init) + { + __pool_ = init; + } + /** + * @brief Set default resources + * @param defaultValue: Value to the default resource + */ + void setDefaultValue(ValType&& defaultValue) + { + __defaultValue_ = { + std::forward<ValType>(defaultValue) + }; + } + + /** + * @brief Get default resources + * @retval Value to the default resource + */ + const ValType& getDefaultValue() + { + return __defaultValue_; + } + + /** + * @brief Get resource associated with key + * @param key: Resource key + * @return Value resource associated with key + */ + const ValType& get(KeyType key); +}; + +#endif // __RESOURCE_MANAGER_STATIC_H diff --git a/x_track/src/Vendor/Simulator/HAL/HAL.cpp b/x_track/src/Vendor/Simulator/HAL/HAL.cpp new file mode 100644 index 0000000000000000000000000000000000000000..55b96a0091348efa5ba458ce39d7903479889697 --- /dev/null +++ b/x_track/src/Vendor/Simulator/HAL/HAL.cpp @@ -0,0 +1,57 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "HAL.h" + +/* Import Device */ +#define HAL_DEF(name) extern DeviceObject DevObj_##name; +#include "HAL_DeviceTree.inc" +#undef HAL_DEF + +DeviceManager* HAL::Manager() +{ + /* Device Array */ + static DeviceObject* devObjArr[] = { +#define HAL_DEF(name) &(DevObj_##name), +#include "HAL_DeviceTree.inc" +#undef HAL_DEF + }; + + static DeviceManager manager(devObjArr, CM_ARRAY_SIZE(devObjArr)); + return &manager; +} + +void HAL::Init() +{ + HAL_Log_Init(); + HAL_LOG_INFO("begin"); + + Manager()->init([](DeviceManager* manager, DeviceObject* dev, int retval) { + if (retval < 0) { + HAL_LOG_ERROR("[%s] init fail: %d", dev->getName(), retval); + } else { + HAL_LOG_INFO("[%s] init success", dev->getName()); + } + }); + + HAL_LOG_INFO("end"); +} diff --git a/x_track/src/Vendor/Simulator/HAL/HAL.h b/x_track/src/Vendor/Simulator/HAL/HAL.h new file mode 100644 index 0000000000000000000000000000000000000000..713a219eb7f71bf2eb190091475eeef16daee77f --- /dev/null +++ b/x_track/src/Vendor/Simulator/HAL/HAL.h @@ -0,0 +1,30 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __HAL_H +#define __HAL_H + +#include "Service/HAL/HAL.h" +#include "Service/HAL/HAL_Log.h" +#include "Utils/CommonMacro/CommonMacro.h" + +#endif diff --git a/x_track/src/Vendor/Simulator/HAL/HAL_Backlight.cpp b/x_track/src/Vendor/Simulator/HAL/HAL_Backlight.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f6d7cdfe7a5952d9353591a424b71869d0f07b58 --- /dev/null +++ b/x_track/src/Vendor/Simulator/HAL/HAL_Backlight.cpp @@ -0,0 +1,105 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "HAL.h" + +#define BACKLIGHT_RESOLUTION 1000 + +namespace HAL { + +class Backlight : private DeviceObject { +public: + Backlight(const char* name) + : DeviceObject(name) + , _backlightValue(0) + { + } + +private: + int _backlightValue; + +private: + virtual int onInit(); + virtual int onRead(void* buffer, size_t size); + virtual int onWrite(const void* buffer, size_t size); + virtual int onIoctl(DeviceObject::IO_Cmd_t cmd, void* data); + int getValue(); + void setValue(int value); + void forceLit(); +}; + +int Backlight::onInit() +{ + setValue(0); + return DeviceObject::RES_OK; +} + +int Backlight::onRead(void* buffer, size_t size) +{ + if (size != sizeof(int)) { + return DeviceObject::RES_PARAM_ERROR; + } + *(int*)buffer = getValue(); + return sizeof(int); +} + +int Backlight::onWrite(const void* buffer, size_t size) +{ + if (size != sizeof(int)) { + return DeviceObject::RES_PARAM_ERROR; + } + setValue(*(int*)buffer); + return sizeof(int); +} + +int Backlight::onIoctl(DeviceObject::IO_Cmd_t cmd, void* data) +{ + int retval = DeviceObject::RES_UNKNOWN; + switch (cmd.full) { + case BACKLIGHT_IOCMD_FORCE_LIT: + forceLit(); + retval = DeviceObject::RES_OK; + break; + default: + break; + } + return retval; +} + +int Backlight::getValue() +{ + return _backlightValue; +} + +void Backlight::setValue(int value) +{ + CM_VALUE_LIMIT(value, 0, BACKLIGHT_RESOLUTION); + _backlightValue = value; +} + +void Backlight::forceLit() +{ +} + +} /* namespace HAL */ + +DEVICE_OBJECT_MAKE(Backlight); diff --git a/x_track/src/Vendor/Simulator/HAL/HAL_Clock.cpp b/x_track/src/Vendor/Simulator/HAL/HAL_Clock.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ec3aad2da4a5829ba76bab28b01de393186222f5 --- /dev/null +++ b/x_track/src/Vendor/Simulator/HAL/HAL_Clock.cpp @@ -0,0 +1,99 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "HAL.h" +#include <stdio.h> +#include <time.h> + +namespace HAL { + +class Clock : private DeviceObject { +public: + Clock(const char* name) + : DeviceObject(name) + { + } + +private: + virtual int onInit(); + virtual int onRead(void* buffer, size_t size); + virtual int onIoctl(DeviceObject::IO_Cmd_t cmd, void* data); + void getInfo(HAL::Clock_Info_t* info); + void calibrate(const HAL::Clock_Info_t* info); +}; + +int Clock::onInit() +{ + return DeviceObject::RES_OK; +} + +int Clock::onRead(void* buffer, size_t size) +{ + if (size != sizeof(HAL::Clock_Info_t)) { + return -1; + } + getInfo((HAL::Clock_Info_t*)buffer); + return sizeof(HAL::Clock_Info_t); +} + +int Clock::onIoctl(DeviceObject::IO_Cmd_t cmd, void* data) +{ + if (cmd.full != CLOCK_IOCMD_CALIBRATE) { + return -1; + } + + calibrate((HAL::Clock_Info_t*)data); + return DeviceObject::RES_OK; +} + +void Clock::getInfo(HAL::Clock_Info_t* info) +{ + struct tm* t; + time_t tt; + time(&tt); + t = localtime(&tt); + + info->year = t->tm_year + 1900; + info->month = t->tm_mon + 1; + info->day = t->tm_mday; + info->week = t->tm_wday; + info->hour = t->tm_hour; + info->minute = t->tm_min; + info->second = t->tm_sec; + info->millisecond = 0; +} + +void Clock::calibrate(const HAL::Clock_Info_t* info) +{ + HAL_LOG_INFO( + "Clock set: %04d-%02d-%02d %02d:%02d:%02d", + info->year, + info->month, + info->day, + info->hour, + info->minute, + info->second); +} + +} /* namespace HAL */ + +DEVICE_OBJECT_MAKE(Clock); diff --git a/x_track/src/Vendor/Simulator/HAL/HAL_DeviceTree.inc b/x_track/src/Vendor/Simulator/HAL/HAL_DeviceTree.inc new file mode 100644 index 0000000000000000000000000000000000000000..f723d1bb1f5de453807fdb653860214bbcac20b3 --- /dev/null +++ b/x_track/src/Vendor/Simulator/HAL/HAL_DeviceTree.inc @@ -0,0 +1,30 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* HAL Device Tree Define */ + +HAL_DEF(Clock) +HAL_DEF(GNSS) +HAL_DEF(Power) +HAL_DEF(Backlight) +HAL_DEF(SdCard) diff --git a/x_track/src/Vendor/Simulator/HAL/HAL_FaultHandle.cpp b/x_track/src/Vendor/Simulator/HAL/HAL_FaultHandle.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e576e08c358f1c7a778634b3cc764cfc4688e074 --- /dev/null +++ b/x_track/src/Vendor/Simulator/HAL/HAL_FaultHandle.cpp @@ -0,0 +1,69 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "HAL.h" +#include "Service/HAL/HAL_Assert.h" +#include "Version.h" +#include <stdlib.h> + +extern "C" { + +void HAL_Assert(const char* file, int line, const char* func, const char* expr) +{ + HAL_LOG_ERROR("Assert: %s:%d %s %s", file, line, func, expr); + HAL_Panic(); +} + +void HAL_Panic(void) +{ + HAL_LOG_ERROR("FXXK PANIC !!!"); + HAL_LOG_ERROR("Firmware: %s", VERSION_FIRMWARE_NAME); + HAL_LOG_ERROR("Software: %s", VERSION_SOFTWARE); + HAL_LOG_ERROR("Hardware: %s", VERSION_HARDWARE); + HAL_LOG_ERROR("Build Time: %s %s", __DATE__, __TIME__); + + exit(EXIT_FAILURE); +} + +} /* extern "C" */ + +namespace HAL { + +class FaultHandle : private DeviceObject { +public: + FaultHandle(const char* name) + : DeviceObject(name) + { + } + +private: + virtual int onInit(); +}; + +int FaultHandle::onInit() +{ + return DeviceObject::RES_OK; +} + +} /* namespace HAL */ + +DEVICE_OBJECT_MAKE(FaultHandle); diff --git a/x_track/src/Vendor/Simulator/HAL/HAL_GNSS.cpp b/x_track/src/Vendor/Simulator/HAL/HAL_GNSS.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5419380c3432f8f93680991d5cfaeb578ca01f0a --- /dev/null +++ b/x_track/src/Vendor/Simulator/HAL/HAL_GNSS.cpp @@ -0,0 +1,323 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Config/Config.h" +#include "HAL.h" +#include "Utils/CommonMacro/CommonMacro.h" +#include "Utils/GPX_Parser/GPX_Parser.h" +#include "Utils/Geo/Geo.h" +#include "lvgl/lvgl.h" +#include <cstdlib> +#include <cstring> +#include <ctime> + +#define CONFIG_GPX_FILE_PATH RESOURCE_MAKE_PATH("/Track/TRK_EXAMPLE.gpx") +#define CONFIG_GPX_DELAY_INIT 5000 +#define CONFIG_RANDOM_GNSS 0 +#define CONFIG_NO_GNSS 0 + +namespace HAL { + +class GNSS : private DeviceObject { +public: + GNSS(const char* name) + : DeviceObject(name) + , _gnssInfo { 0 } + , _parser(parserAvaliable, parserReadByte, this) + , _file { 0 } + , _size { 0 } + , _prePoint { 0 } + , _isReset(false) + , _isUpdate(false) + { + _gnssInfo.longitude = CONFIG_GNSS_LONGITUDE_DEFAULT; + _gnssInfo.latitude = CONFIG_GNSS_LATITUDE_DEFAULT; + } + +private: + HAL::GNSS_Info_t _gnssInfo; + HAL::GNSS_GSV_Item_t _gsvItems[12]; + GPX_Parser _parser; + lv_fs_file_t _file; + uint32_t _size; + GPX_Parser::Point_t _prePoint; + bool _isReset; + bool _isUpdate; + +private: + virtual int onInit(); + virtual int onRead(void* buffer, size_t size); + virtual int onWrite(const void* buffer, size_t size); + virtual int onIoctl(DeviceObject::IO_Cmd_t cmd, void* data); + bool parserInit(); + void paserStart(); + void paserUpdate(); + static int parserReadByte(GPX_Parser* parser); + static int parserAvaliable(GPX_Parser* parser); + time_t makeTime(const GPX_Parser::Time_t* time); + double getDiffTime(const GPX_Parser::Time_t* time1, const GPX_Parser::Time_t* time2); +#if CONFIG_RANDOM_GNSS + void makeRandomGNSSInfo(HAL::GNSS_Info_t* gnssInfo); +#endif /* CONFIG_RANDOM_GNSS */ +}; + +int GNSS::onInit() +{ +#if !CONFIG_NO_GNSS + if (parserInit()) { + _gnssInfo.isVaild = true; + _gnssInfo.satellites = 10; + + /* Delay start GPX parser */ + lv_timer_t* timer = lv_timer_create( + [](lv_timer_t* tmr) { + auto ctx = (GNSS*)lv_timer_get_user_data(tmr); + ctx->paserStart(); + }, + CONFIG_GPX_DELAY_INIT, + this); + lv_timer_set_repeat_count(timer, 1); + } +#endif + + return DeviceObject::RES_OK; +} + +int GNSS::onRead(void* buffer, size_t size) +{ + if (size != sizeof(HAL::GNSS_Info_t)) { + return DeviceObject::RES_PARAM_ERROR; + } + + HAL::GNSS_Info_t* gnssInfo = (HAL::GNSS_Info_t*)buffer; + memset(gnssInfo, 0, sizeof(HAL::GNSS_Info_t)); + + if (_size) { + *gnssInfo = _gnssInfo; + } else { +#if CONFIG_RANDOM_GNSS + makeRandomGNSSInfo(gnssInfo); +#else + gnssInfo->longitude = CONFIG_GNSS_LONGITUDE_DEFAULT; + gnssInfo->latitude = CONFIG_GNSS_LATITUDE_DEFAULT; +#ifndef CONFIG_NO_GNSS + gnssInfo->isVaild = true; + gnssInfo->satellites = 10; +#endif /* CONFIG_NO_GNSS */ +#endif /* CONFIG_RANDOM_GNSS */ + } + + /* GPGSV Info */ + gnssInfo->gsv.items = _gsvItems; + gnssInfo->gsv.num = rand() % (CM_ARRAY_SIZE(_gsvItems) + 1); + gnssInfo->gsv.satsInView = rand() % 10; + + DeviceObject* devClock = HAL::Manager()->getDevice("Clock"); + if (devClock) { + devClock->read(&gnssInfo->clock, sizeof(gnssInfo->clock)); + } + + return sizeof(HAL::GNSS_Info_t); +} + +int GNSS::onWrite(const void* buffer, size_t size) +{ + return size; +} + +int GNSS::onIoctl(DeviceObject::IO_Cmd_t cmd, void* data) +{ + switch (cmd.full) { + case GNSS_IOCMD_UPDATE: { + if (_isUpdate) { + _isUpdate = false; + return DeviceObject::RES_OK; + } + + return DeviceObject::RES_UNSUPPORT; + } + + default: + break; + } + + return DeviceObject::RES_UNKNOWN; +} + +int GNSS::parserReadByte(GPX_Parser* parser) +{ + auto ctx = (GNSS*)parser->getUserData(); + uint8_t data; + uint32_t rd; + lv_fs_res_t res = lv_fs_read(&ctx->_file, &data, sizeof(data), &rd); + + if (res != LV_FS_RES_OK || rd != sizeof(data)) { + return 0; + } + + return data; +} + +int GNSS::parserAvaliable(GPX_Parser* parser) +{ + auto ctx = (GNSS*)parser->getUserData(); + + uint32_t cur; + lv_fs_res_t res = lv_fs_tell(&ctx->_file, &cur); + + if (res != LV_FS_RES_OK) { + return 0; + } + + return (ctx->_size - cur); +} + +bool GNSS::parserInit() +{ + lv_fs_res_t res = lv_fs_open(&_file, CONFIG_GPX_FILE_PATH, LV_FS_MODE_RD); + + if (res != LV_FS_RES_OK) { + return false; + } + + lv_fs_seek(&_file, 0, LV_FS_SEEK_END); + lv_fs_tell(&_file, &_size); + lv_fs_seek(&_file, 0, LV_FS_SEEK_SET); + return true; +} + +void GNSS::paserStart() +{ + lv_timer_create( + [](lv_timer_t* timer) { + auto ctx = (GNSS*)lv_timer_get_user_data(timer); + ctx->paserUpdate(); + }, + CONFIG_GNSS_UPDATE_PERIOD, + this); +} + +time_t GNSS::makeTime(const GPX_Parser::Time_t* time) +{ + struct tm t; + memset(&t, 0, sizeof(t)); + t.tm_year = time->year - 1900; + t.tm_mon = time->month; + t.tm_mday = time->day; + t.tm_hour = time->hour; + t.tm_min = time->minute; + t.tm_sec = time->second; + + return mktime(&t); +} + +double GNSS::getDiffTime(const GPX_Parser::Time_t* time1, const GPX_Parser::Time_t* time2) +{ + time_t t1 = makeTime(time1); + time_t t2 = makeTime(time2); + return difftime(t1, t2); +} + +void GNSS::paserUpdate() +{ + GPX_Parser::Point_t point = { 0 }; + int parserFlag = _parser.getNext(&point); + + if (parserFlag & GPX_Parser::FLAG_LATITUDE && parserFlag & GPX_Parser::FLAG_LONGITUDE) { + if (!_isReset) { + _gnssInfo.longitude = point.longitude; + _gnssInfo.latitude = point.latitude; + _gnssInfo.altitude = point.altitude; + + _prePoint = point; + _isReset = true; + return; + } + + double distance = Geo::distanceBetween( + _gnssInfo.latitude, _gnssInfo.longitude, + point.latitude, point.longitude); + + double diffTime = CONFIG_GNSS_UPDATE_PERIOD / 1000.0; + + if (parserFlag & GPX_Parser::FLAG_TIME) { + diffTime = getDiffTime(&point.time, &_prePoint.time); + } + + if (std::abs(diffTime) >= 0.0001) { + _gnssInfo.speed = (float)(distance / diffTime) * 3.6f; + } + + _gnssInfo.course = (float)Geo::courseTo( + _gnssInfo.latitude, + _gnssInfo.longitude, + point.latitude, + point.longitude); + + _gnssInfo.longitude = point.longitude; + _gnssInfo.latitude = point.latitude; + _gnssInfo.altitude = point.altitude; + _prePoint = point; + + /* Gererate random GSV info */ + int index = rand() % CM_ARRAY_SIZE(_gsvItems); + _gsvItems[index].id = rand() % 32; + _gsvItems[index].elevation = rand() % 90; + _gsvItems[index].azimuth = rand() % 360; + _gsvItems[index].snr = rand() % 100; + + _isUpdate = true; + } else if (parserFlag & GPX_Parser::FLAG_END_OF_FILE) { + lv_fs_seek(&_file, 0, LV_FS_SEEK_SET); + _isReset = false; + } +} + +#if CONFIG_RANDOM_GNSS + +void GNSS::makeRandomGNSSInfo(HAL::GNSS_Info_t* gnssInfo) +{ + static int cnt = 0; + static float speed = 0; + + cnt++; + float diff = rand() % 200 / 10.0f - 10; + speed += diff; + CM_VALUE_LIMIT(speed, 0, 200); + + if (cnt > 100) { + cnt = 0; + } + + gnssInfo->latitude = CONFIG_GNSS_LATITUDE_DEFAULT + (rand() % 2000 - 1000) / 100000.0f; + gnssInfo->longitude = CONFIG_GNSS_LONGITUDE_DEFAULT + (rand() % 2000 - 1000) / 100000.0f; + gnssInfo->speed = speed; + gnssInfo->course = rand() % 360; + gnssInfo->satellites = cnt < 30 ? 0 : rand() % 20 + 5; + gnssInfo->isVaild = cnt > 30; +} + +#endif + +} /* namespace HAL */ + +DEVICE_OBJECT_MAKE(GNSS); diff --git a/x_track/src/Vendor/Simulator/HAL/HAL_Log.cpp b/x_track/src/Vendor/Simulator/HAL/HAL_Log.cpp new file mode 100644 index 0000000000000000000000000000000000000000..29d8d8a5e6b9f3872ec5ed30f3a19e00ff51feee --- /dev/null +++ b/x_track/src/Vendor/Simulator/HAL/HAL_Log.cpp @@ -0,0 +1,70 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "HAL.h" +#include <inttypes.h> +#include <stdarg.h> +#include <stdio.h> + +extern "C" { + +void HAL_Log_Init() +{ +} + +void HAL_Log_PrintString(const char* str) +{ + printf("%s", str); +} + +void HAL_Log(uint8_t level, const char* func, const char* fmt, ...) +{ + if (level >= _HAL_LOG_LEVEL_LAST) { + return; + } + + static const char* prompt[_HAL_LOG_LEVEL_LAST] = { + "Info", "Warn", "Error" + }; + + char buffer[256]; + + va_list ap; + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + printf("[HAL][%s] %s: %s\n", prompt[level], func, buffer); +} + +void HAL_Log_Printf(const char* fmt, ...) +{ + char buffer[256]; + + va_list ap; + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + printf("%s", buffer); +} +} /* extern "C" */ diff --git a/x_track/src/Vendor/Simulator/HAL/HAL_Power.cpp b/x_track/src/Vendor/Simulator/HAL/HAL_Power.cpp new file mode 100644 index 0000000000000000000000000000000000000000..25e6af5dbdccceb5cd7962d456be021616566bcf --- /dev/null +++ b/x_track/src/Vendor/Simulator/HAL/HAL_Power.cpp @@ -0,0 +1,89 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "HAL.h" +#include <stdlib.h> + +namespace HAL { + +class Power : private DeviceObject { +public: + Power(const char* name) + : DeviceObject(name) + { + } + +private: + virtual int onInit(); + virtual int onRead(void* buffer, size_t size); + virtual int onIoctl(DeviceObject::IO_Cmd_t cmd, void* data); +}; + +int Power::onInit() +{ + return DeviceObject::RES_OK; +} + +int Power::onRead(void* buffer, size_t size) +{ + if (size != sizeof(HAL::Power_Info_t)) { + return DeviceObject::RES_PARAM_ERROR; + } + + HAL::Power_Info_t* info = (HAL::Power_Info_t*)buffer; + info->voltage = rand() % 1000 + 3200; + info->level = rand() % 100; + info->isReady = (rand() % 2) == 0; + info->isCharging = (rand() % 2) == 0; + + return sizeof(HAL::Power_Info_t); +} + +int Power::onIoctl(DeviceObject::IO_Cmd_t cmd, void* data) +{ + int retval = 0; + + switch (cmd.full) { + case POWER_IOCMD_POWER_ON: + HAL_LOG_INFO("Power: ON"); + break; + case POWER_IOCMD_POWER_OFF: + HAL_LOG_INFO("Power: OFF"); + retval = system("poweroff"); + break; + case POWER_IOCMD_REBOOT: + HAL_LOG_INFO("Power: Reboot"); + break; + case POWER_IOCMD_GAUGE_RESET: + HAL_LOG_INFO("Power: Reset Fuel Gauge"); + break; + case POWER_IOCMD_SET_SYS_VOLTAGE: + break; + default: + retval = -1; + } + return retval; +} + +} /* namespace HAL */ + +DEVICE_OBJECT_MAKE(Power); diff --git a/x_track/src/Vendor/Simulator/HAL/HAL_SdCard.cpp b/x_track/src/Vendor/Simulator/HAL/HAL_SdCard.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e897fcbdd36baaaa34f519f54884f4e9baaa8dee --- /dev/null +++ b/x_track/src/Vendor/Simulator/HAL/HAL_SdCard.cpp @@ -0,0 +1,113 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 HanfG, _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "Config/Config.h" +#include "HAL.h" +#include <cstring> +#include <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#define SD_CARD_GB(byte) ((uint64_t)(byte)*1024 * 1024 * 1024) + +namespace HAL { + +class SdCard : private DeviceObject { +public: + SdCard(const char* name) + : DeviceObject(name) + { + } + +private: + HAL::SdCard_Info_t _info; + +private: + virtual int onInit(); + virtual int onRead(void* buffer, size_t size); + virtual int onIoctl(DeviceObject::IO_Cmd_t cmd, void* data); + const char* genRealPath(const char* path); +}; + +int SdCard::onInit() +{ + _info.isInsert = true; + _info.freeCapacity = SD_CARD_GB(16); + _info.totalCapacity = SD_CARD_GB(32); + return DeviceObject::RES_OK; +} + +int SdCard::onRead(void* buffer, size_t size) +{ + if (size != sizeof(_info)) { + return DeviceObject::RES_PARAM_ERROR; + } + + memcpy(buffer, &_info, sizeof(_info)); + + return sizeof(_info); +} + +int SdCard::onIoctl(DeviceObject::IO_Cmd_t cmd, void* data) +{ + switch (cmd.full) { + case SDCARD_IOCMD_MOUNT: + return DeviceObject::RES_OK; + + case SDCARD_IOCMD_UNMOUNT: + return DeviceObject::RES_OK; + + case SDCARD_IOCMD_MKDIR: { + const char* realPath = genRealPath((const char*)data); + int ret = access(realPath, F_OK); + if (ret == 0) { + HAL_LOG_INFO("%s already exits", realPath); + return DeviceObject::RES_OK; + } + return mkdir(realPath, 0755); + } + + case SDCARD_IOCMD_REMOVE: + return remove(genRealPath((const char*)data)); + + default: + break; + } + + return DeviceObject::RES_UNSUPPORT; +} + +const char* SdCard::genRealPath(const char* path) +{ + if (*path != '\0') { + path++; + if (*path == ':' || *path == '/') { + path++; + } + } + return path; +} + +} /* namespace HAL */ + +DEVICE_OBJECT_MAKE(SdCard); diff --git a/x_track/src/Vendor/Simulator/HAL/HAL_Tick.cpp b/x_track/src/Vendor/Simulator/HAL/HAL_Tick.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b023dde4a9df873062f27b593d05e22e881205df --- /dev/null +++ b/x_track/src/Vendor/Simulator/HAL/HAL_Tick.cpp @@ -0,0 +1,78 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "HAL.h" +#include <time.h> + +#ifdef WIN32 +#include <stdint.h> +#include <windows.h> + +#define CLOCK_MONOTONIC 0 +#define EPOCHFILETIME (116444736000000000ULL) + +static int clock_gettime(int, struct timespec* tv) +{ + if (!tv) { + return -1; + } + + FILETIME ft; + uint64_t tmpres = 0; + GetSystemTimeAsFileTime(&ft); + tmpres |= ft.dwHighDateTime; + tmpres <<= 32; + tmpres |= ft.dwLowDateTime; + + tmpres /= 10; // Convert 100 nanoseconds intervals to microseconds + tmpres -= EPOCHFILETIME; // Subtract time since epoch + + tv->tv_sec = static_cast<long>(tmpres / 1000000UL); + tv->tv_nsec = static_cast<long>(tmpres % 1000000UL) * 1000; + + return 0; +} +#endif + +uint32_t HAL::GetTick() +{ + struct timespec ts; + uint32_t ms; + clock_gettime(CLOCK_MONOTONIC, &ts); + ms = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; + return ms; +} + +uint32_t HAL::GetTickElaps(uint32_t prevTick) +{ + uint32_t actTick = GetTick(); + + /*If there is no overflow in sys_time simple subtract*/ + if (actTick >= prevTick) { + prevTick = actTick - prevTick; + } else { + prevTick = UINT32_MAX - prevTick + 1; + prevTick += actTick; + } + + return prevTick; +} diff --git a/x_track/src/Vendor/Simulator/lv_port/lv_port.h b/x_track/src/Vendor/Simulator/lv_port/lv_port.h new file mode 100644 index 0000000000000000000000000000000000000000..0a363f700ebf69ebe83d28aa84483cc424f638b1 --- /dev/null +++ b/x_track/src/Vendor/Simulator/lv_port/lv_port.h @@ -0,0 +1,69 @@ +/* + * MIT License + * Copyright (c) 2023 - 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file lv_port.h + * + */ + +#ifndef LV_PORT_H +#define LV_PORT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include <stdint.h> + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +int lv_port_init(void); + +uint32_t lv_port_tick_get(void); + +void lv_port_profiler_init(void); + +void lv_port_sleep(uint32_t ms); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_PORT_H*/ diff --git a/x_track/src/Vendor/Simulator/lv_port/lv_port_profiler.c b/x_track/src/Vendor/Simulator/lv_port/lv_port_profiler.c new file mode 100644 index 0000000000000000000000000000000000000000..3297e2e3fa979ecbfc50301a91579a203882885e --- /dev/null +++ b/x_track/src/Vendor/Simulator/lv_port/lv_port_profiler.c @@ -0,0 +1,60 @@ +/* + * MIT License + * Copyright (c) 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "lv_port.h" +#include "lvgl/lvgl.h" +#include <stdio.h> +#include <time.h> + +#if LV_USE_PROFILER + +static uint32_t profiler_get_tick_us(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000000 + ts.tv_nsec / 1000; +} + +static void profiler_flush(const char* buf) +{ + printf("%s", buf); +} + +void lv_port_profiler_init(void) +{ + lv_profiler_builtin_config_t config; + lv_profiler_builtin_config_init(&config); + config.buf_size = 64 * 1024; + config.tick_per_sec = 1000000; + config.tick_get_cb = profiler_get_tick_us; + config.flush_cb = profiler_flush; + lv_profiler_builtin_init(&config); +} + +#else + +void lv_port_profiler_init(void) +{ + /*Do nothing*/ +} + +#endif \ No newline at end of file diff --git a/x_track/src/Vendor/Simulator/lv_port/lv_port_sdl2.c b/x_track/src/Vendor/Simulator/lv_port/lv_port_sdl2.c new file mode 100644 index 0000000000000000000000000000000000000000..77bfe7aaa7e15d55e352ab8bec01656f83f301ad --- /dev/null +++ b/x_track/src/Vendor/Simulator/lv_port/lv_port_sdl2.c @@ -0,0 +1,133 @@ +/* + * MIT License + * Copyright (c) 2023 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file lv_port_sdl2.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lvgl/lvgl.h" + +#if LV_USE_SDL + +#include "lv_port.h" +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +/********************* + * DEFINES + *********************/ + +#ifndef LV_SCREEN_HOR_RES +#define LV_SCREEN_HOR_RES 240 +#endif + +#ifndef LV_SCREEN_VER_RES +#define LV_SCREEN_VER_RES 320 +#endif + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void touchpad_cursor_init(lv_indev_t* indev, lv_coord_t size); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ +int lv_port_init(void) +{ + lv_disp_t* disp = lv_sdl_window_create(LV_SCREEN_HOR_RES, LV_SCREEN_VER_RES); + lv_indev_t* mouse = lv_sdl_mouse_create(); + lv_indev_set_group(mouse, lv_group_get_default()); + lv_indev_set_disp(mouse, disp); + +#ifdef LV_INDEV_CURSOR_SIZE + touchpad_cursor_init(mouse, LV_INDEV_CURSOR_SIZE); + lv_indev_t* mousewheel = lv_sdl_mousewheel_create(); + lv_indev_set_disp(mousewheel, disp); + lv_indev_set_group(mousewheel, lv_group_get_default()); +#endif + + lv_indev_t* keyboard = lv_sdl_keyboard_create(); + lv_indev_set_disp(keyboard, disp); + lv_indev_set_group(keyboard, lv_group_get_default()); + + lv_port_profiler_init(); + + return 0; +} + +uint32_t lv_port_tick_get(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +void lv_port_sleep(uint32_t ms) +{ + usleep(ms * 1000); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void touchpad_cursor_init(lv_indev_t* indev, lv_coord_t size) +{ + if (size <= 0) { + return; + } + + lv_obj_t* cursor = lv_obj_create(lv_layer_sys()); + lv_obj_remove_style_all(cursor); + + lv_obj_set_size(cursor, size, size); + lv_obj_set_style_translate_x(cursor, -size / 2, 0); + lv_obj_set_style_translate_y(cursor, -size / 2, 0); + lv_obj_set_style_radius(cursor, LV_RADIUS_CIRCLE, 0); + lv_obj_set_style_bg_opa(cursor, LV_OPA_50, 0); + lv_obj_set_style_bg_color(cursor, lv_color_black(), 0); + lv_obj_set_style_border_width(cursor, 2, 0); + lv_obj_set_style_border_color(cursor, lv_palette_main(LV_PALETTE_GREY), 0); + lv_indev_set_cursor(indev, cursor); +} + +#endif /*USE_SDL*/ diff --git a/x_track/x_track_main.cpp b/x_track/x_track_main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b9a7cc25ea9503c78bc548e7911a4fbb14b2b2b7 --- /dev/null +++ b/x_track/x_track_main.cpp @@ -0,0 +1,102 @@ +/* + * MIT License + * Copyright (c) 2024 _VIFEXTech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "App/App.h" +#include "HAL/HAL.h" +#include "lvgl/lvgl.h" +#include <uv.h> + +static void lv_nuttx_uv_loop_run(uv_loop_t* loop, lv_nuttx_result_t* result) +{ + lv_nuttx_uv_t uv_info; + void* data; + + uv_loop_init(loop); + + lv_memset(&uv_info, 0, sizeof(uv_info)); + uv_info.loop = loop; + uv_info.disp = result->disp; + uv_info.indev = result->indev; +#ifdef CONFIG_UINPUT_TOUCH + uv_info.uindev = result->utouch_indev; +#endif + + data = lv_nuttx_uv_init(&uv_info); + uv_run(loop, UV_RUN_DEFAULT); + lv_nuttx_uv_deinit(&data); +} + +/** + * @brief Main Function + * @param argc: Argument count + * @param argv: Argument vector + * @retval error code + */ +extern "C" int main(int argc, const char* argv[]) +{ + lv_nuttx_dsc_t info; + lv_nuttx_result_t result; + uv_loop_t ui_loop; + lv_memzero(&ui_loop, sizeof(uv_loop_t)); + + if (lv_is_initialized()) { + LV_LOG_ERROR("LVGL already initialized! aborting."); + return -1; + } + + lv_init(); + lv_nuttx_dsc_init(&info); + lv_nuttx_init(&info, &result); + + if (result.disp == NULL) { + LV_LOG_ERROR("Display initialization failure!"); + return 1; + } + + HAL::Init(); + + AppContext_t* appCtx = App_CreateContext(argc, argv); + LV_LOG_USER("App context created: %p", appCtx); + + /* Create app timer */ + lv_timer_t* app_timer = lv_timer_create( + [](lv_timer_t* tmr) { + auto ctx = (AppContext_t*)lv_timer_get_user_data(tmr); + + /* Run app loop */ + uint32_t app_idle_time = App_RunLoopExecute(ctx); + + lv_timer_set_period(tmr, LV_MIN(app_idle_time, 1000)); + }, + 0, + appCtx); + + /* Run UI loop */ + lv_nuttx_uv_loop_run(&ui_loop, &result); + + /* Clean up */ + lv_timer_delete(app_timer); + App_DestroyContext(appCtx); + lv_nuttx_deinit(&result); + lv_deinit(); + return 0; +}